//===- CopyConfig.cpp -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "CopyConfig.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/CRC.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compression.h" #include "llvm/Support/Errc.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/StringSaver.h" #include namespace llvm { namespace objcopy { namespace { enum ObjcopyID { OBJCOPY_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ OBJCOPY_##ID, #include "ObjcopyOpts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) const char *const OBJCOPY_##NAME[] = VALUE; #include "ObjcopyOpts.inc" #undef PREFIX static const opt::OptTable::Info ObjcopyInfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ {OBJCOPY_##PREFIX, \ NAME, \ HELPTEXT, \ METAVAR, \ OBJCOPY_##ID, \ opt::Option::KIND##Class, \ PARAM, \ FLAGS, \ OBJCOPY_##GROUP, \ OBJCOPY_##ALIAS, \ ALIASARGS, \ VALUES}, #include "ObjcopyOpts.inc" #undef OPTION }; class ObjcopyOptTable : public opt::OptTable { public: ObjcopyOptTable() : OptTable(ObjcopyInfoTable) {} }; enum InstallNameToolID { INSTALL_NAME_TOOL_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ INSTALL_NAME_TOOL_##ID, #include "InstallNameToolOpts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) \ const char *const INSTALL_NAME_TOOL_##NAME[] = VALUE; #include "InstallNameToolOpts.inc" #undef PREFIX static const opt::OptTable::Info InstallNameToolInfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ {INSTALL_NAME_TOOL_##PREFIX, \ NAME, \ HELPTEXT, \ METAVAR, \ INSTALL_NAME_TOOL_##ID, \ opt::Option::KIND##Class, \ PARAM, \ FLAGS, \ INSTALL_NAME_TOOL_##GROUP, \ INSTALL_NAME_TOOL_##ALIAS, \ ALIASARGS, \ VALUES}, #include "InstallNameToolOpts.inc" #undef OPTION }; class InstallNameToolOptTable : public opt::OptTable { public: InstallNameToolOptTable() : OptTable(InstallNameToolInfoTable) {} }; enum BitcodeStripID { BITCODE_STRIP_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ BITCODE_STRIP_##ID, #include "BitcodeStripOpts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) const char *const BITCODE_STRIP_##NAME[] = VALUE; #include "BitcodeStripOpts.inc" #undef PREFIX static const opt::OptTable::Info BitcodeStripInfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ {BITCODE_STRIP_##PREFIX, \ NAME, \ HELPTEXT, \ METAVAR, \ BITCODE_STRIP_##ID, \ opt::Option::KIND##Class, \ PARAM, \ FLAGS, \ BITCODE_STRIP_##GROUP, \ BITCODE_STRIP_##ALIAS, \ ALIASARGS, \ VALUES}, #include "BitcodeStripOpts.inc" #undef OPTION }; class BitcodeStripOptTable : public opt::OptTable { public: BitcodeStripOptTable() : OptTable(BitcodeStripInfoTable) {} }; enum StripID { STRIP_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ STRIP_##ID, #include "StripOpts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) const char *const STRIP_##NAME[] = VALUE; #include "StripOpts.inc" #undef PREFIX static const opt::OptTable::Info StripInfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ {STRIP_##PREFIX, NAME, HELPTEXT, \ METAVAR, STRIP_##ID, opt::Option::KIND##Class, \ PARAM, FLAGS, STRIP_##GROUP, \ STRIP_##ALIAS, ALIASARGS, VALUES}, #include "StripOpts.inc" #undef OPTION }; class StripOptTable : public opt::OptTable { public: StripOptTable() : OptTable(StripInfoTable) {} }; } // namespace static SectionFlag parseSectionRenameFlag(StringRef SectionName) { return llvm::StringSwitch(SectionName) .CaseLower("alloc", SectionFlag::SecAlloc) .CaseLower("load", SectionFlag::SecLoad) .CaseLower("noload", SectionFlag::SecNoload) .CaseLower("readonly", SectionFlag::SecReadonly) .CaseLower("debug", SectionFlag::SecDebug) .CaseLower("code", SectionFlag::SecCode) .CaseLower("data", SectionFlag::SecData) .CaseLower("rom", SectionFlag::SecRom) .CaseLower("merge", SectionFlag::SecMerge) .CaseLower("strings", SectionFlag::SecStrings) .CaseLower("contents", SectionFlag::SecContents) .CaseLower("share", SectionFlag::SecShare) .CaseLower("exclude", SectionFlag::SecExclude) .Default(SectionFlag::SecNone); } static Expected parseSectionFlagSet(ArrayRef SectionFlags) { SectionFlag ParsedFlags = SectionFlag::SecNone; for (StringRef Flag : SectionFlags) { SectionFlag ParsedFlag = parseSectionRenameFlag(Flag); if (ParsedFlag == SectionFlag::SecNone) return createStringError( errc::invalid_argument, "unrecognized section flag '%s'. Flags supported for GNU " "compatibility: alloc, load, noload, readonly, exclude, debug, " "code, data, rom, share, contents, merge, strings", Flag.str().c_str()); ParsedFlags |= ParsedFlag; } return ParsedFlags; } static Expected parseRenameSectionValue(StringRef FlagValue) { if (!FlagValue.contains('=')) return createStringError(errc::invalid_argument, "bad format for --rename-section: missing '='"); // Initial split: ".foo" = ".bar,f1,f2,..." auto Old2New = FlagValue.split('='); SectionRename SR; SR.OriginalName = Old2New.first; // Flags split: ".bar" "f1" "f2" ... SmallVector NameAndFlags; Old2New.second.split(NameAndFlags, ','); SR.NewName = NameAndFlags[0]; if (NameAndFlags.size() > 1) { Expected ParsedFlagSet = parseSectionFlagSet(makeArrayRef(NameAndFlags).drop_front()); if (!ParsedFlagSet) return ParsedFlagSet.takeError(); SR.NewFlags = *ParsedFlagSet; } return SR; } static Expected> parseSetSectionAlignment(StringRef FlagValue) { if (!FlagValue.contains('=')) return createStringError( errc::invalid_argument, "bad format for --set-section-alignment: missing '='"); auto Split = StringRef(FlagValue).split('='); if (Split.first.empty()) return createStringError( errc::invalid_argument, "bad format for --set-section-alignment: missing section name"); uint64_t NewAlign; if (Split.second.getAsInteger(0, NewAlign)) return createStringError(errc::invalid_argument, "invalid alignment for --set-section-alignment: '%s'", Split.second.str().c_str()); return std::make_pair(Split.first, NewAlign); } static Expected parseSetSectionFlagValue(StringRef FlagValue) { if (!StringRef(FlagValue).contains('=')) return createStringError(errc::invalid_argument, "bad format for --set-section-flags: missing '='"); // Initial split: ".foo" = "f1,f2,..." auto Section2Flags = StringRef(FlagValue).split('='); SectionFlagsUpdate SFU; SFU.Name = Section2Flags.first; // Flags split: "f1" "f2" ... SmallVector SectionFlags; Section2Flags.second.split(SectionFlags, ','); Expected ParsedFlagSet = parseSectionFlagSet(SectionFlags); if (!ParsedFlagSet) return ParsedFlagSet.takeError(); SFU.NewFlags = *ParsedFlagSet; return SFU; } struct TargetInfo { FileFormat Format; MachineInfo Machine; }; // FIXME: consolidate with the bfd parsing used by lld. static const StringMap TargetMap{ // Name, {EMachine, 64bit, LittleEndian} // x86 {"elf32-i386", {ELF::EM_386, false, true}}, {"elf32-x86-64", {ELF::EM_X86_64, false, true}}, {"elf64-x86-64", {ELF::EM_X86_64, true, true}}, // Intel MCU {"elf32-iamcu", {ELF::EM_IAMCU, false, true}}, // ARM {"elf32-littlearm", {ELF::EM_ARM, false, true}}, // ARM AArch64 {"elf64-aarch64", {ELF::EM_AARCH64, true, true}}, {"elf64-littleaarch64", {ELF::EM_AARCH64, true, true}}, // RISC-V {"elf32-littleriscv", {ELF::EM_RISCV, false, true}}, {"elf64-littleriscv", {ELF::EM_RISCV, true, true}}, // PowerPC {"elf32-powerpc", {ELF::EM_PPC, false, false}}, {"elf32-powerpcle", {ELF::EM_PPC, false, true}}, {"elf64-powerpc", {ELF::EM_PPC64, true, false}}, {"elf64-powerpcle", {ELF::EM_PPC64, true, true}}, // MIPS {"elf32-bigmips", {ELF::EM_MIPS, false, false}}, {"elf32-ntradbigmips", {ELF::EM_MIPS, false, false}}, {"elf32-ntradlittlemips", {ELF::EM_MIPS, false, true}}, {"elf32-tradbigmips", {ELF::EM_MIPS, false, false}}, {"elf32-tradlittlemips", {ELF::EM_MIPS, false, true}}, {"elf64-tradbigmips", {ELF::EM_MIPS, true, false}}, {"elf64-tradlittlemips", {ELF::EM_MIPS, true, true}}, // SPARC {"elf32-sparc", {ELF::EM_SPARC, false, false}}, {"elf32-sparcel", {ELF::EM_SPARC, false, true}}, {"elf32-hexagon", {ELF::EM_HEXAGON, false, true}}, }; static Expected getOutputTargetInfoByTargetName(StringRef TargetName) { StringRef OriginalTargetName = TargetName; bool IsFreeBSD = TargetName.consume_back("-freebsd"); auto Iter = TargetMap.find(TargetName); if (Iter == std::end(TargetMap)) return createStringError(errc::invalid_argument, "invalid output format: '%s'", OriginalTargetName.str().c_str()); MachineInfo MI = Iter->getValue(); if (IsFreeBSD) MI.OSABI = ELF::ELFOSABI_FREEBSD; FileFormat Format; if (TargetName.startswith("elf")) Format = FileFormat::ELF; else // This should never happen because `TargetName` is valid (it certainly // exists in the TargetMap). llvm_unreachable("unknown target prefix"); return {TargetInfo{Format, MI}}; } static Error addSymbolsFromFile(NameMatcher &Symbols, BumpPtrAllocator &Alloc, StringRef Filename, MatchStyle MS, llvm::function_ref ErrorCallback) { StringSaver Saver(Alloc); SmallVector Lines; auto BufOrErr = MemoryBuffer::getFile(Filename); if (!BufOrErr) return createFileError(Filename, BufOrErr.getError()); BufOrErr.get()->getBuffer().split(Lines, '\n'); for (StringRef Line : Lines) { // Ignore everything after '#', trim whitespace, and only add the symbol if // it's not empty. auto TrimmedLine = Line.split('#').first.trim(); if (!TrimmedLine.empty()) if (Error E = Symbols.addMatcher(NameOrPattern::create( Saver.save(TrimmedLine), MS, ErrorCallback))) return E; } return Error::success(); } Expected NameOrPattern::create(StringRef Pattern, MatchStyle MS, llvm::function_ref ErrorCallback) { switch (MS) { case MatchStyle::Literal: return NameOrPattern(Pattern); case MatchStyle::Wildcard: { SmallVector Data; bool IsPositiveMatch = true; if (Pattern[0] == '!') { IsPositiveMatch = false; Pattern = Pattern.drop_front(); } Expected GlobOrErr = GlobPattern::create(Pattern); // If we couldn't create it as a glob, report the error, but try again with // a literal if the error reporting is non-fatal. if (!GlobOrErr) { if (Error E = ErrorCallback(GlobOrErr.takeError())) return std::move(E); return create(Pattern, MatchStyle::Literal, ErrorCallback); } return NameOrPattern(std::make_shared(*GlobOrErr), IsPositiveMatch); } case MatchStyle::Regex: { SmallVector Data; return NameOrPattern(std::make_shared( ("^" + Pattern.ltrim('^').rtrim('$') + "$").toStringRef(Data))); } } llvm_unreachable("Unhandled llvm.objcopy.MatchStyle enum"); } static Error addSymbolsToRenameFromFile(StringMap &SymbolsToRename, BumpPtrAllocator &Alloc, StringRef Filename) { StringSaver Saver(Alloc); SmallVector Lines; auto BufOrErr = MemoryBuffer::getFile(Filename); if (!BufOrErr) return createFileError(Filename, BufOrErr.getError()); BufOrErr.get()->getBuffer().split(Lines, '\n'); size_t NumLines = Lines.size(); for (size_t LineNo = 0; LineNo < NumLines; ++LineNo) { StringRef TrimmedLine = Lines[LineNo].split('#').first.trim(); if (TrimmedLine.empty()) continue; std::pair Pair = Saver.save(TrimmedLine).split(' '); StringRef NewName = Pair.second.trim(); if (NewName.empty()) return createStringError(errc::invalid_argument, "%s:%zu: missing new symbol name", Filename.str().c_str(), LineNo + 1); SymbolsToRename.insert({Pair.first, NewName}); } return Error::success(); } template static ErrorOr getAsInteger(StringRef Val) { T Result; if (Val.getAsInteger(0, Result)) return errc::invalid_argument; return Result; } namespace { enum class ToolType { Objcopy, Strip, InstallNameTool, BitcodeStrip }; } // anonymous namespace static void printHelp(const opt::OptTable &OptTable, raw_ostream &OS, ToolType Tool) { StringRef HelpText, ToolName; switch (Tool) { case ToolType::Objcopy: ToolName = "llvm-objcopy"; HelpText = " [options] input [output]"; break; case ToolType::Strip: ToolName = "llvm-strip"; HelpText = " [options] inputs..."; break; case ToolType::InstallNameTool: ToolName = "llvm-install-name-tool"; HelpText = " [options] input"; break; case ToolType::BitcodeStrip: ToolName = "llvm-bitcode-strip"; HelpText = " [options] input"; break; } OptTable.PrintHelp(OS, (ToolName + HelpText).str().c_str(), (ToolName + " tool").str().c_str()); // TODO: Replace this with libOption call once it adds extrahelp support. // The CommandLine library has a cl::extrahelp class to support this, // but libOption does not have that yet. OS << "\nPass @FILE as argument to read options from FILE.\n"; } // ParseObjcopyOptions returns the config and sets the input arguments. If a // help flag is set then ParseObjcopyOptions will print the help messege and // exit. Expected parseObjcopyOptions(ArrayRef ArgsArr, llvm::function_ref ErrorCallback) { DriverConfig DC; ObjcopyOptTable T; unsigned MissingArgumentIndex, MissingArgumentCount; llvm::opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); if (InputArgs.size() == 0) { printHelp(T, errs(), ToolType::Objcopy); exit(1); } if (InputArgs.hasArg(OBJCOPY_help)) { printHelp(T, outs(), ToolType::Objcopy); exit(0); } if (InputArgs.hasArg(OBJCOPY_version)) { outs() << "llvm-objcopy, compatible with GNU objcopy\n"; cl::PrintVersionMessage(); exit(0); } SmallVector Positional; for (auto Arg : InputArgs.filtered(OBJCOPY_UNKNOWN)) return createStringError(errc::invalid_argument, "unknown argument '%s'", Arg->getAsString(InputArgs).c_str()); for (auto Arg : InputArgs.filtered(OBJCOPY_INPUT)) Positional.push_back(Arg->getValue()); if (Positional.empty()) return createStringError(errc::invalid_argument, "no input file specified"); if (Positional.size() > 2) return createStringError(errc::invalid_argument, "too many positional arguments"); CopyConfig Config; Config.InputFilename = Positional[0]; Config.OutputFilename = Positional[Positional.size() == 1 ? 0 : 1]; if (InputArgs.hasArg(OBJCOPY_target) && (InputArgs.hasArg(OBJCOPY_input_target) || InputArgs.hasArg(OBJCOPY_output_target))) return createStringError( errc::invalid_argument, "--target cannot be used with --input-target or --output-target"); if (InputArgs.hasArg(OBJCOPY_regex) && InputArgs.hasArg(OBJCOPY_wildcard)) return createStringError(errc::invalid_argument, "--regex and --wildcard are incompatible"); MatchStyle SectionMatchStyle = InputArgs.hasArg(OBJCOPY_regex) ? MatchStyle::Regex : MatchStyle::Wildcard; MatchStyle SymbolMatchStyle = InputArgs.hasArg(OBJCOPY_regex) ? MatchStyle::Regex : InputArgs.hasArg(OBJCOPY_wildcard) ? MatchStyle::Wildcard : MatchStyle::Literal; StringRef InputFormat, OutputFormat; if (InputArgs.hasArg(OBJCOPY_target)) { InputFormat = InputArgs.getLastArgValue(OBJCOPY_target); OutputFormat = InputArgs.getLastArgValue(OBJCOPY_target); } else { InputFormat = InputArgs.getLastArgValue(OBJCOPY_input_target); OutputFormat = InputArgs.getLastArgValue(OBJCOPY_output_target); } // FIXME: Currently, we ignore the target for non-binary/ihex formats // explicitly specified by -I option (e.g. -Ielf32-x86-64) and guess the // format by llvm::object::createBinary regardless of the option value. Config.InputFormat = StringSwitch(InputFormat) .Case("binary", FileFormat::Binary) .Case("ihex", FileFormat::IHex) .Default(FileFormat::Unspecified); if (InputArgs.hasArg(OBJCOPY_new_symbol_visibility)) Config.NewSymbolVisibility = InputArgs.getLastArgValue(OBJCOPY_new_symbol_visibility); Config.OutputFormat = StringSwitch(OutputFormat) .Case("binary", FileFormat::Binary) .Case("ihex", FileFormat::IHex) .Default(FileFormat::Unspecified); if (Config.OutputFormat == FileFormat::Unspecified) { if (OutputFormat.empty()) { Config.OutputFormat = Config.InputFormat; } else { Expected Target = getOutputTargetInfoByTargetName(OutputFormat); if (!Target) return Target.takeError(); Config.OutputFormat = Target->Format; Config.OutputArch = Target->Machine; } } if (auto Arg = InputArgs.getLastArg(OBJCOPY_compress_debug_sections, OBJCOPY_compress_debug_sections_eq)) { Config.CompressionType = DebugCompressionType::Z; if (Arg->getOption().getID() == OBJCOPY_compress_debug_sections_eq) { Config.CompressionType = StringSwitch( InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections_eq)) .Case("zlib-gnu", DebugCompressionType::GNU) .Case("zlib", DebugCompressionType::Z) .Default(DebugCompressionType::None); if (Config.CompressionType == DebugCompressionType::None) return createStringError( errc::invalid_argument, "invalid or unsupported --compress-debug-sections format: %s", InputArgs.getLastArgValue(OBJCOPY_compress_debug_sections_eq) .str() .c_str()); } if (!zlib::isAvailable()) return createStringError( errc::invalid_argument, "LLVM was not compiled with LLVM_ENABLE_ZLIB: can not compress"); } Config.AddGnuDebugLink = InputArgs.getLastArgValue(OBJCOPY_add_gnu_debuglink); // The gnu_debuglink's target is expected to not change or else its CRC would // become invalidated and get rejected. We can avoid recalculating the // checksum for every target file inside an archive by precomputing the CRC // here. This prevents a significant amount of I/O. if (!Config.AddGnuDebugLink.empty()) { auto DebugOrErr = MemoryBuffer::getFile(Config.AddGnuDebugLink); if (!DebugOrErr) return createFileError(Config.AddGnuDebugLink, DebugOrErr.getError()); auto Debug = std::move(*DebugOrErr); Config.GnuDebugLinkCRC32 = llvm::crc32(arrayRefFromStringRef(Debug->getBuffer())); } Config.SplitDWO = InputArgs.getLastArgValue(OBJCOPY_split_dwo); Config.SymbolsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_symbols); Config.AllocSectionsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_alloc_sections); if (auto Arg = InputArgs.getLastArg(OBJCOPY_extract_partition)) Config.ExtractPartition = Arg->getValue(); for (auto Arg : InputArgs.filtered(OBJCOPY_redefine_symbol)) { if (!StringRef(Arg->getValue()).contains('=')) return createStringError(errc::invalid_argument, "bad format for --redefine-sym"); auto Old2New = StringRef(Arg->getValue()).split('='); if (!Config.SymbolsToRename.insert(Old2New).second) return createStringError(errc::invalid_argument, "multiple redefinition of symbol '%s'", Old2New.first.str().c_str()); } for (auto Arg : InputArgs.filtered(OBJCOPY_redefine_symbols)) if (Error E = addSymbolsToRenameFromFile(Config.SymbolsToRename, DC.Alloc, Arg->getValue())) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_rename_section)) { Expected SR = parseRenameSectionValue(StringRef(Arg->getValue())); if (!SR) return SR.takeError(); if (!Config.SectionsToRename.try_emplace(SR->OriginalName, *SR).second) return createStringError(errc::invalid_argument, "multiple renames of section '%s'", SR->OriginalName.str().c_str()); } for (auto Arg : InputArgs.filtered(OBJCOPY_set_section_alignment)) { Expected> NameAndAlign = parseSetSectionAlignment(Arg->getValue()); if (!NameAndAlign) return NameAndAlign.takeError(); Config.SetSectionAlignment[NameAndAlign->first] = NameAndAlign->second; } for (auto Arg : InputArgs.filtered(OBJCOPY_set_section_flags)) { Expected SFU = parseSetSectionFlagValue(Arg->getValue()); if (!SFU) return SFU.takeError(); if (!Config.SetSectionFlags.try_emplace(SFU->Name, *SFU).second) return createStringError( errc::invalid_argument, "--set-section-flags set multiple times for section '%s'", SFU->Name.str().c_str()); } // Prohibit combinations of --set-section-flags when the section name is used // by --rename-section, either as a source or a destination. for (const auto &E : Config.SectionsToRename) { const SectionRename &SR = E.second; if (Config.SetSectionFlags.count(SR.OriginalName)) return createStringError( errc::invalid_argument, "--set-section-flags=%s conflicts with --rename-section=%s=%s", SR.OriginalName.str().c_str(), SR.OriginalName.str().c_str(), SR.NewName.str().c_str()); if (Config.SetSectionFlags.count(SR.NewName)) return createStringError( errc::invalid_argument, "--set-section-flags=%s conflicts with --rename-section=%s=%s", SR.NewName.str().c_str(), SR.OriginalName.str().c_str(), SR.NewName.str().c_str()); } for (auto Arg : InputArgs.filtered(OBJCOPY_remove_section)) if (Error E = Config.ToRemove.addMatcher(NameOrPattern::create( Arg->getValue(), SectionMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_keep_section)) if (Error E = Config.KeepSection.addMatcher(NameOrPattern::create( Arg->getValue(), SectionMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_only_section)) if (Error E = Config.OnlySection.addMatcher(NameOrPattern::create( Arg->getValue(), SectionMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_add_section)) { StringRef ArgValue(Arg->getValue()); if (!ArgValue.contains('=')) return createStringError(errc::invalid_argument, "bad format for --add-section: missing '='"); if (ArgValue.split("=").second.empty()) return createStringError( errc::invalid_argument, "bad format for --add-section: missing file name"); Config.AddSection.push_back(ArgValue); } for (auto Arg : InputArgs.filtered(OBJCOPY_dump_section)) Config.DumpSection.push_back(Arg->getValue()); Config.StripAll = InputArgs.hasArg(OBJCOPY_strip_all); Config.StripAllGNU = InputArgs.hasArg(OBJCOPY_strip_all_gnu); Config.StripDebug = InputArgs.hasArg(OBJCOPY_strip_debug); Config.StripDWO = InputArgs.hasArg(OBJCOPY_strip_dwo); Config.StripSections = InputArgs.hasArg(OBJCOPY_strip_sections); Config.StripNonAlloc = InputArgs.hasArg(OBJCOPY_strip_non_alloc); Config.StripUnneeded = InputArgs.hasArg(OBJCOPY_strip_unneeded); Config.ExtractDWO = InputArgs.hasArg(OBJCOPY_extract_dwo); Config.ExtractMainPartition = InputArgs.hasArg(OBJCOPY_extract_main_partition); Config.LocalizeHidden = InputArgs.hasArg(OBJCOPY_localize_hidden); Config.Weaken = InputArgs.hasArg(OBJCOPY_weaken); if (InputArgs.hasArg(OBJCOPY_discard_all, OBJCOPY_discard_locals)) Config.DiscardMode = InputArgs.hasFlag(OBJCOPY_discard_all, OBJCOPY_discard_locals) ? DiscardType::All : DiscardType::Locals; Config.OnlyKeepDebug = InputArgs.hasArg(OBJCOPY_only_keep_debug); Config.KeepFileSymbols = InputArgs.hasArg(OBJCOPY_keep_file_symbols); Config.DecompressDebugSections = InputArgs.hasArg(OBJCOPY_decompress_debug_sections); if (Config.DiscardMode == DiscardType::All) { Config.StripDebug = true; Config.KeepFileSymbols = true; } for (auto Arg : InputArgs.filtered(OBJCOPY_localize_symbol)) if (Error E = Config.SymbolsToLocalize.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_localize_symbols)) if (Error E = addSymbolsFromFile(Config.SymbolsToLocalize, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_keep_global_symbol)) if (Error E = Config.SymbolsToKeepGlobal.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_keep_global_symbols)) if (Error E = addSymbolsFromFile(Config.SymbolsToKeepGlobal, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_globalize_symbol)) if (Error E = Config.SymbolsToGlobalize.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_globalize_symbols)) if (Error E = addSymbolsFromFile(Config.SymbolsToGlobalize, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_weaken_symbol)) if (Error E = Config.SymbolsToWeaken.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_weaken_symbols)) if (Error E = addSymbolsFromFile(Config.SymbolsToWeaken, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_strip_symbol)) if (Error E = Config.SymbolsToRemove.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_strip_symbols)) if (Error E = addSymbolsFromFile(Config.SymbolsToRemove, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_strip_unneeded_symbol)) if (Error E = Config.UnneededSymbolsToRemove.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_strip_unneeded_symbols)) if (Error E = addSymbolsFromFile(Config.UnneededSymbolsToRemove, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_keep_symbol)) if (Error E = Config.SymbolsToKeep.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_keep_symbols)) if (Error E = addSymbolsFromFile(Config.SymbolsToKeep, DC.Alloc, Arg->getValue(), SymbolMatchStyle, ErrorCallback)) return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_add_symbol)) Config.SymbolsToAdd.push_back(Arg->getValue()); Config.AllowBrokenLinks = InputArgs.hasArg(OBJCOPY_allow_broken_links); Config.DeterministicArchives = InputArgs.hasFlag( OBJCOPY_enable_deterministic_archives, OBJCOPY_disable_deterministic_archives, /*default=*/true); Config.PreserveDates = InputArgs.hasArg(OBJCOPY_preserve_dates); if (Config.PreserveDates && (Config.OutputFilename == "-" || Config.InputFilename == "-")) return createStringError(errc::invalid_argument, "--preserve-dates requires a file"); for (auto Arg : InputArgs) if (Arg->getOption().matches(OBJCOPY_set_start)) { auto EAddr = getAsInteger(Arg->getValue()); if (!EAddr) return createStringError( EAddr.getError(), "bad entry point address: '%s'", Arg->getValue()); Config.EntryExpr = [EAddr](uint64_t) { return *EAddr; }; } else if (Arg->getOption().matches(OBJCOPY_change_start)) { auto EIncr = getAsInteger(Arg->getValue()); if (!EIncr) return createStringError(EIncr.getError(), "bad entry point increment: '%s'", Arg->getValue()); auto Expr = Config.EntryExpr ? std::move(Config.EntryExpr) : [](uint64_t A) { return A; }; Config.EntryExpr = [Expr, EIncr](uint64_t EAddr) { return Expr(EAddr) + *EIncr; }; } if (Config.DecompressDebugSections && Config.CompressionType != DebugCompressionType::None) { return createStringError( errc::invalid_argument, "cannot specify both --compress-debug-sections and " "--decompress-debug-sections"); } if (Config.DecompressDebugSections && !zlib::isAvailable()) return createStringError( errc::invalid_argument, "LLVM was not compiled with LLVM_ENABLE_ZLIB: cannot decompress"); if (Config.ExtractPartition && Config.ExtractMainPartition) return createStringError(errc::invalid_argument, "cannot specify --extract-partition together with " "--extract-main-partition"); DC.CopyConfigs.push_back(std::move(Config)); return std::move(DC); } // ParseInstallNameToolOptions returns the config and sets the input arguments. // If a help flag is set then ParseInstallNameToolOptions will print the help // messege and exit. Expected parseInstallNameToolOptions(ArrayRef ArgsArr) { DriverConfig DC; CopyConfig Config; InstallNameToolOptTable T; unsigned MissingArgumentIndex, MissingArgumentCount; llvm::opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); if (MissingArgumentCount) return createStringError( errc::invalid_argument, "missing argument to " + StringRef(InputArgs.getArgString(MissingArgumentIndex)) + " option"); if (InputArgs.size() == 0) { printHelp(T, errs(), ToolType::InstallNameTool); exit(1); } if (InputArgs.hasArg(INSTALL_NAME_TOOL_help)) { printHelp(T, outs(), ToolType::InstallNameTool); exit(0); } if (InputArgs.hasArg(INSTALL_NAME_TOOL_version)) { outs() << "llvm-install-name-tool, compatible with cctools " "install_name_tool\n"; cl::PrintVersionMessage(); exit(0); } for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_add_rpath)) Config.RPathToAdd.push_back(Arg->getValue()); for (auto *Arg : InputArgs.filtered(INSTALL_NAME_TOOL_prepend_rpath)) Config.RPathToPrepend.push_back(Arg->getValue()); for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_delete_rpath)) { StringRef RPath = Arg->getValue(); // Cannot add and delete the same rpath at the same time. if (is_contained(Config.RPathToAdd, RPath)) return createStringError( errc::invalid_argument, "cannot specify both -add_rpath '%s' and -delete_rpath '%s'", RPath.str().c_str(), RPath.str().c_str()); if (is_contained(Config.RPathToPrepend, RPath)) return createStringError( errc::invalid_argument, "cannot specify both -prepend_rpath '%s' and -delete_rpath '%s'", RPath.str().c_str(), RPath.str().c_str()); Config.RPathsToRemove.insert(RPath); } for (auto *Arg : InputArgs.filtered(INSTALL_NAME_TOOL_rpath)) { StringRef Old = Arg->getValue(0); StringRef New = Arg->getValue(1); auto Match = [=](StringRef RPath) { return RPath == Old || RPath == New; }; // Cannot specify duplicate -rpath entries auto It1 = find_if( Config.RPathsToUpdate, [&Match](const DenseMap::value_type &OldNew) { return Match(OldNew.getFirst()) || Match(OldNew.getSecond()); }); if (It1 != Config.RPathsToUpdate.end()) return createStringError(errc::invalid_argument, "cannot specify both -rpath '" + It1->getFirst() + "' '" + It1->getSecond() + "' and -rpath '" + Old + "' '" + New + "'"); // Cannot specify the same rpath under both -delete_rpath and -rpath auto It2 = find_if(Config.RPathsToRemove, Match); if (It2 != Config.RPathsToRemove.end()) return createStringError(errc::invalid_argument, "cannot specify both -delete_rpath '" + *It2 + "' and -rpath '" + Old + "' '" + New + "'"); // Cannot specify the same rpath under both -add_rpath and -rpath auto It3 = find_if(Config.RPathToAdd, Match); if (It3 != Config.RPathToAdd.end()) return createStringError(errc::invalid_argument, "cannot specify both -add_rpath '" + *It3 + "' and -rpath '" + Old + "' '" + New + "'"); // Cannot specify the same rpath under both -prepend_rpath and -rpath. auto It4 = find_if(Config.RPathToPrepend, Match); if (It4 != Config.RPathToPrepend.end()) return createStringError(errc::invalid_argument, "cannot specify both -prepend_rpath '" + *It4 + "' and -rpath '" + Old + "' '" + New + "'"); Config.RPathsToUpdate.insert({Old, New}); } if (auto *Arg = InputArgs.getLastArg(INSTALL_NAME_TOOL_id)) { Config.SharedLibId = Arg->getValue(); if (Config.SharedLibId->empty()) return createStringError(errc::invalid_argument, "cannot specify an empty id"); } for (auto *Arg : InputArgs.filtered(INSTALL_NAME_TOOL_change)) Config.InstallNamesToUpdate.insert({Arg->getValue(0), Arg->getValue(1)}); Config.RemoveAllRpaths = InputArgs.hasArg(INSTALL_NAME_TOOL_delete_all_rpaths); SmallVector Positional; for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_UNKNOWN)) return createStringError(errc::invalid_argument, "unknown argument '%s'", Arg->getAsString(InputArgs).c_str()); for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_INPUT)) Positional.push_back(Arg->getValue()); if (Positional.empty()) return createStringError(errc::invalid_argument, "no input file specified"); if (Positional.size() > 1) return createStringError( errc::invalid_argument, "llvm-install-name-tool expects a single input file"); Config.InputFilename = Positional[0]; Config.OutputFilename = Positional[0]; DC.CopyConfigs.push_back(std::move(Config)); return std::move(DC); } Expected parseBitcodeStripOptions(ArrayRef ArgsArr) { DriverConfig DC; CopyConfig Config; BitcodeStripOptTable T; unsigned MissingArgumentIndex, MissingArgumentCount; opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); if (InputArgs.size() == 0) { printHelp(T, errs(), ToolType::BitcodeStrip); exit(1); } if (InputArgs.hasArg(BITCODE_STRIP_help)) { printHelp(T, outs(), ToolType::BitcodeStrip); exit(0); } if (InputArgs.hasArg(BITCODE_STRIP_version)) { outs() << "llvm-bitcode-strip, compatible with cctools " "bitcode_strip\n"; cl::PrintVersionMessage(); exit(0); } for (auto *Arg : InputArgs.filtered(BITCODE_STRIP_UNKNOWN)) return createStringError(errc::invalid_argument, "unknown argument '%s'", Arg->getAsString(InputArgs).c_str()); SmallVector Positional; for (auto *Arg : InputArgs.filtered(BITCODE_STRIP_INPUT)) Positional.push_back(Arg->getValue()); if (Positional.size() > 1) return createStringError(errc::invalid_argument, "llvm-bitcode-strip expects a single input file"); assert(!Positional.empty()); Config.InputFilename = Positional[0]; Config.OutputFilename = Positional[0]; DC.CopyConfigs.push_back(std::move(Config)); return std::move(DC); } // ParseStripOptions returns the config and sets the input arguments. If a // help flag is set then ParseStripOptions will print the help messege and // exit. Expected parseStripOptions(ArrayRef ArgsArr, llvm::function_ref ErrorCallback) { StripOptTable T; unsigned MissingArgumentIndex, MissingArgumentCount; llvm::opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); if (InputArgs.size() == 0) { printHelp(T, errs(), ToolType::Strip); exit(1); } if (InputArgs.hasArg(STRIP_help)) { printHelp(T, outs(), ToolType::Strip); exit(0); } if (InputArgs.hasArg(STRIP_version)) { outs() << "llvm-strip, compatible with GNU strip\n"; cl::PrintVersionMessage(); exit(0); } SmallVector Positional; for (auto Arg : InputArgs.filtered(STRIP_UNKNOWN)) return createStringError(errc::invalid_argument, "unknown argument '%s'", Arg->getAsString(InputArgs).c_str()); for (auto Arg : InputArgs.filtered(STRIP_INPUT)) Positional.push_back(Arg->getValue()); if (Positional.empty()) return createStringError(errc::invalid_argument, "no input file specified"); if (Positional.size() > 1 && InputArgs.hasArg(STRIP_output)) return createStringError( errc::invalid_argument, "multiple input files cannot be used in combination with -o"); CopyConfig Config; if (InputArgs.hasArg(STRIP_regex) && InputArgs.hasArg(STRIP_wildcard)) return createStringError(errc::invalid_argument, "--regex and --wildcard are incompatible"); MatchStyle SectionMatchStyle = InputArgs.hasArg(STRIP_regex) ? MatchStyle::Regex : MatchStyle::Wildcard; MatchStyle SymbolMatchStyle = InputArgs.hasArg(STRIP_regex) ? MatchStyle::Regex : InputArgs.hasArg(STRIP_wildcard) ? MatchStyle::Wildcard : MatchStyle::Literal; Config.AllowBrokenLinks = InputArgs.hasArg(STRIP_allow_broken_links); Config.StripDebug = InputArgs.hasArg(STRIP_strip_debug); if (InputArgs.hasArg(STRIP_discard_all, STRIP_discard_locals)) Config.DiscardMode = InputArgs.hasFlag(STRIP_discard_all, STRIP_discard_locals) ? DiscardType::All : DiscardType::Locals; Config.StripSections = InputArgs.hasArg(STRIP_strip_sections); Config.StripUnneeded = InputArgs.hasArg(STRIP_strip_unneeded); if (auto Arg = InputArgs.getLastArg(STRIP_strip_all, STRIP_no_strip_all)) Config.StripAll = Arg->getOption().getID() == STRIP_strip_all; Config.StripAllGNU = InputArgs.hasArg(STRIP_strip_all_gnu); Config.StripSwiftSymbols = InputArgs.hasArg(STRIP_strip_swift_symbols); Config.OnlyKeepDebug = InputArgs.hasArg(STRIP_only_keep_debug); Config.KeepFileSymbols = InputArgs.hasArg(STRIP_keep_file_symbols); for (auto Arg : InputArgs.filtered(STRIP_keep_section)) if (Error E = Config.KeepSection.addMatcher(NameOrPattern::create( Arg->getValue(), SectionMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(STRIP_remove_section)) if (Error E = Config.ToRemove.addMatcher(NameOrPattern::create( Arg->getValue(), SectionMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(STRIP_strip_symbol)) if (Error E = Config.SymbolsToRemove.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); for (auto Arg : InputArgs.filtered(STRIP_keep_symbol)) if (Error E = Config.SymbolsToKeep.addMatcher(NameOrPattern::create( Arg->getValue(), SymbolMatchStyle, ErrorCallback))) return std::move(E); if (!InputArgs.hasArg(STRIP_no_strip_all) && !Config.StripDebug && !Config.StripUnneeded && Config.DiscardMode == DiscardType::None && !Config.StripAllGNU && Config.SymbolsToRemove.empty()) Config.StripAll = true; if (Config.DiscardMode == DiscardType::All) { Config.StripDebug = true; Config.KeepFileSymbols = true; } Config.DeterministicArchives = InputArgs.hasFlag(STRIP_enable_deterministic_archives, STRIP_disable_deterministic_archives, /*default=*/true); Config.PreserveDates = InputArgs.hasArg(STRIP_preserve_dates); Config.InputFormat = FileFormat::Unspecified; Config.OutputFormat = FileFormat::Unspecified; DriverConfig DC; if (Positional.size() == 1) { Config.InputFilename = Positional[0]; Config.OutputFilename = InputArgs.getLastArgValue(STRIP_output, Positional[0]); DC.CopyConfigs.push_back(std::move(Config)); } else { StringMap InputFiles; for (StringRef Filename : Positional) { if (InputFiles[Filename]++ == 1) { if (Filename == "-") return createStringError( errc::invalid_argument, "cannot specify '-' as an input file more than once"); if (Error E = ErrorCallback(createStringError( errc::invalid_argument, "'%s' was already specified", Filename.str().c_str()))) return std::move(E); } Config.InputFilename = Filename; Config.OutputFilename = Filename; DC.CopyConfigs.push_back(Config); } } if (Config.PreserveDates && (is_contained(Positional, "-") || InputArgs.getLastArgValue(STRIP_output) == "-")) return createStringError(errc::invalid_argument, "--preserve-dates requires a file"); return std::move(DC); } } // namespace objcopy } // namespace llvm