//===- llvm-ifs.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 "ErrorCollector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Triple.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/InterfaceStub/ELFObjHandler.h" #include "llvm/InterfaceStub/IFSHandler.h" #include "llvm/InterfaceStub/IFSStub.h" #include "llvm/ObjectYAML/yaml2obj.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/VersionTuple.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TextAPI/InterfaceFile.h" #include "llvm/TextAPI/TextAPIReader.h" #include "llvm/TextAPI/TextAPIWriter.h" #include #include #include #include using namespace llvm; using namespace llvm::yaml; using namespace llvm::MachO; using namespace llvm::ifs; #define DEBUG_TYPE "llvm-ifs" namespace { const VersionTuple IfsVersionCurrent(3, 0); enum class FileFormat { IFS, ELF, TBD }; } // end anonymous namespace using namespace llvm::opt; enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ OPT_##ID, #include "Opts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) \ static constexpr StringLiteral NAME##_init[] = VALUE; \ static constexpr ArrayRef NAME(NAME##_init, \ std::size(NAME##_init) - 1); #include "Opts.inc" #undef PREFIX static constexpr opt::OptTable::Info InfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ { \ PREFIX, NAME, HELPTEXT, \ METAVAR, OPT_##ID, opt::Option::KIND##Class, \ PARAM, FLAGS, OPT_##GROUP, \ OPT_##ALIAS, ALIASARGS, VALUES}, #include "Opts.inc" #undef OPTION }; class IFSOptTable : public opt::GenericOptTable { public: IFSOptTable() : opt::GenericOptTable(InfoTable) { setGroupedShortOptions(true); } }; struct DriverConfig { std::vector InputFilePaths; std::optional InputFormat; std::optional OutputFormat; std::optional HintIfsTarget; std::optional OptTargetTriple; std::optional OverrideArch; std::optional OverrideBitWidth; std::optional OverrideEndianness; bool StripIfsArch = false; bool StripIfsBitwidth = false; bool StripIfsEndianness = false; bool StripIfsTarget = false; bool StripNeeded = false; bool StripSize = false; bool StripUndefined = false; std::vector Exclude; std::optional SoName; std::optional Output; std::optional OutputElf; std::optional OutputIfs; std::optional OutputTbd; bool WriteIfChanged = false; }; static std::string getTypeName(IFSSymbolType Type) { switch (Type) { case IFSSymbolType::NoType: return "NoType"; case IFSSymbolType::Func: return "Func"; case IFSSymbolType::Object: return "Object"; case IFSSymbolType::TLS: return "TLS"; case IFSSymbolType::Unknown: return "Unknown"; } llvm_unreachable("Unexpected ifs symbol type."); } static Expected> readInputFile(std::optional &InputFormat, StringRef FilePath) { // Read in file. ErrorOr> BufOrError = MemoryBuffer::getFileOrSTDIN(FilePath, /*IsText=*/true); if (!BufOrError) return createStringError(BufOrError.getError(), "Could not open `%s`", FilePath.data()); std::unique_ptr FileReadBuffer = std::move(*BufOrError); ErrorCollector EC(/*UseFatalErrors=*/false); // First try to read as a binary (fails fast if not binary). if (!InputFormat || *InputFormat == FileFormat::ELF) { Expected> StubFromELF = readELFFile(FileReadBuffer->getMemBufferRef()); if (StubFromELF) { InputFormat = FileFormat::ELF; (*StubFromELF)->IfsVersion = IfsVersionCurrent; return std::move(*StubFromELF); } EC.addError(StubFromELF.takeError(), "BinaryRead"); } // Fall back to reading as a ifs. if (!InputFormat || *InputFormat == FileFormat::IFS) { Expected> StubFromIFS = readIFSFromBuffer(FileReadBuffer->getBuffer()); if (StubFromIFS) { InputFormat = FileFormat::IFS; if ((*StubFromIFS)->IfsVersion > IfsVersionCurrent) EC.addError( createStringError(errc::not_supported, "IFS version " + (*StubFromIFS)->IfsVersion.getAsString() + " is unsupported."), "ReadInputFile"); else return std::move(*StubFromIFS); } else { EC.addError(StubFromIFS.takeError(), "YamlParse"); } } // If both readers fail, build a new error that includes all information. EC.addError(createStringError(errc::not_supported, "No file readers succeeded reading `%s` " "(unsupported/malformed file?)", FilePath.data()), "ReadInputFile"); EC.escalateToFatal(); return EC.makeError(); } static int writeTbdStub(const Triple &T, const std::vector &Symbols, const StringRef Format, raw_ostream &Out) { auto PlatformTypeOrError = [](const llvm::Triple &T) -> llvm::Expected { if (T.isMacOSX()) return llvm::MachO::PLATFORM_MACOS; if (T.isTvOS()) return llvm::MachO::PLATFORM_TVOS; if (T.isWatchOS()) return llvm::MachO::PLATFORM_WATCHOS; // Note: put isiOS last because tvOS and watchOS are also iOS according // to the Triple. if (T.isiOS()) return llvm::MachO::PLATFORM_IOS; return createStringError(errc::not_supported, "Invalid Platform.\n"); }(T); if (!PlatformTypeOrError) return -1; PlatformType Plat = PlatformTypeOrError.get(); TargetList Targets({Target(llvm::MachO::mapToArchitecture(T), Plat)}); InterfaceFile File; File.setFileType(FileType::TBD_V3); // Only supporting v3 for now. File.addTargets(Targets); for (const auto &Symbol : Symbols) { auto Name = Symbol.Name; auto Kind = SymbolKind::GlobalSymbol; switch (Symbol.Type) { default: case IFSSymbolType::NoType: Kind = SymbolKind::GlobalSymbol; break; case IFSSymbolType::Object: Kind = SymbolKind::GlobalSymbol; break; case IFSSymbolType::Func: Kind = SymbolKind::GlobalSymbol; break; } if (Symbol.Weak) File.addSymbol(Kind, Name, Targets, SymbolFlags::WeakDefined); else File.addSymbol(Kind, Name, Targets); } SmallString<4096> Buffer; raw_svector_ostream OS(Buffer); if (Error Result = TextAPIWriter::writeToStream(OS, File)) return -1; Out << OS.str(); return 0; } static void fatalError(Error Err) { WithColor::defaultErrorHandler(std::move(Err)); exit(1); } static void fatalError(Twine T) { WithColor::error() << T.str() << '\n'; exit(1); } /// writeIFS() writes a Text-Based ELF stub to a file using the latest version /// of the YAML parser. static Error writeIFS(StringRef FilePath, IFSStub &Stub, bool WriteIfChanged) { // Write IFS to memory first. std::string IFSStr; raw_string_ostream OutStr(IFSStr); Error YAMLErr = writeIFSToOutputStream(OutStr, Stub); if (YAMLErr) return YAMLErr; OutStr.flush(); if (WriteIfChanged) { if (ErrorOr> BufOrError = MemoryBuffer::getFile(FilePath)) { // Compare IFS output with the existing IFS file. If unchanged, avoid // changing the file. if ((*BufOrError)->getBuffer() == IFSStr) return Error::success(); } } // Open IFS file for writing. std::error_code SysErr; raw_fd_ostream Out(FilePath, SysErr); if (SysErr) return createStringError(SysErr, "Couldn't open `%s` for writing", FilePath.data()); Out << IFSStr; return Error::success(); } static DriverConfig parseArgs(int argc, char *const *argv) { BumpPtrAllocator A; StringSaver Saver(A); IFSOptTable Tbl; StringRef ToolName = argv[0]; llvm::opt::InputArgList Args = Tbl.parseArgs( argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { fatalError(Msg); }); if (Args.hasArg(OPT_help)) { Tbl.printHelp(llvm::outs(), (Twine(ToolName) + " [options]") .str() .c_str(), "shared object stubbing tool"); std::exit(0); } if (Args.hasArg(OPT_version)) { llvm::outs() << ToolName << '\n'; cl::PrintVersionMessage(); std::exit(0); } DriverConfig Config; for (const opt::Arg *A : Args.filtered(OPT_INPUT)) Config.InputFilePaths.push_back(A->getValue()); if (const opt::Arg *A = Args.getLastArg(OPT_input_format_EQ)) { Config.InputFormat = StringSwitch>(A->getValue()) .Case("IFS", FileFormat::IFS) .Case("ELF", FileFormat::ELF) .Default(std::nullopt); if (!Config.InputFormat) fatalError(Twine("invalid argument '") + A->getValue()); } auto OptionNotFound = [ToolName](StringRef FlagName, StringRef OptionName) { fatalError(Twine(ToolName) + ": for the " + FlagName + " option: Cannot find option named '" + OptionName + "'!"); }; if (const opt::Arg *A = Args.getLastArg(OPT_output_format_EQ)) { Config.OutputFormat = StringSwitch>(A->getValue()) .Case("IFS", FileFormat::IFS) .Case("ELF", FileFormat::ELF) .Case("TBD", FileFormat::TBD) .Default(std::nullopt); if (!Config.OutputFormat) OptionNotFound("--output-format", A->getValue()); } if (const opt::Arg *A = Args.getLastArg(OPT_arch_EQ)) Config.OverrideArch = ELF::convertArchNameToEMachine(A->getValue()); if (const opt::Arg *A = Args.getLastArg(OPT_bitwidth_EQ)) { size_t Width; llvm::StringRef S(A->getValue()); if (!S.getAsInteger(10, Width) || Width == 64 || Width == 32) Config.OverrideBitWidth = Width == 64 ? IFSBitWidthType::IFS64 : IFSBitWidthType::IFS32; else OptionNotFound("--bitwidth", A->getValue()); } if (const opt::Arg *A = Args.getLastArg(OPT_endianness_EQ)) { Config.OverrideEndianness = StringSwitch>(A->getValue()) .Case("little", IFSEndiannessType::Little) .Case("big", IFSEndiannessType::Big) .Default(std::nullopt); if (!Config.OverrideEndianness) OptionNotFound("--endianness", A->getValue()); } if (const opt::Arg *A = Args.getLastArg(OPT_target_EQ)) Config.OptTargetTriple = A->getValue(); if (const opt::Arg *A = Args.getLastArg(OPT_hint_ifs_target_EQ)) Config.HintIfsTarget = A->getValue(); Config.StripIfsArch = Args.hasArg(OPT_strip_ifs_arch); Config.StripIfsBitwidth = Args.hasArg(OPT_strip_ifs_bitwidth); Config.StripIfsEndianness = Args.hasArg(OPT_strip_ifs_endianness); Config.StripIfsTarget = Args.hasArg(OPT_strip_ifs_target); Config.StripUndefined = Args.hasArg(OPT_strip_undefined); Config.StripNeeded = Args.hasArg(OPT_strip_needed); Config.StripSize = Args.hasArg(OPT_strip_size); for (const opt::Arg *A : Args.filtered(OPT_exclude_EQ)) Config.Exclude.push_back(A->getValue()); if (const opt::Arg *A = Args.getLastArg(OPT_soname_EQ)) Config.SoName = A->getValue(); if (const opt::Arg *A = Args.getLastArg(OPT_output_EQ)) Config.Output = A->getValue(); if (const opt::Arg *A = Args.getLastArg(OPT_output_elf_EQ)) Config.OutputElf = A->getValue(); if (const opt::Arg *A = Args.getLastArg(OPT_output_ifs_EQ)) Config.OutputIfs = A->getValue(); if (const opt::Arg *A = Args.getLastArg(OPT_output_tbd_EQ)) Config.OutputTbd = A->getValue(); Config.WriteIfChanged = Args.hasArg(OPT_write_if_changed); return Config; } int llvm_ifs_main(int argc, char **argv) { DriverConfig Config = parseArgs(argc, argv); if (Config.InputFilePaths.empty()) Config.InputFilePaths.push_back("-"); // If input files are more than one, they can only be IFS files. if (Config.InputFilePaths.size() > 1) Config.InputFormat = FileFormat::IFS; // Attempt to merge input. IFSStub Stub; std::map SymbolMap; std::string PreviousInputFilePath; for (const std::string &InputFilePath : Config.InputFilePaths) { Expected> StubOrErr = readInputFile(Config.InputFormat, InputFilePath); if (!StubOrErr) fatalError(StubOrErr.takeError()); std::unique_ptr TargetStub = std::move(StubOrErr.get()); if (PreviousInputFilePath.empty()) { Stub.IfsVersion = TargetStub->IfsVersion; Stub.Target = TargetStub->Target; Stub.SoName = TargetStub->SoName; Stub.NeededLibs = TargetStub->NeededLibs; } else { if (Stub.IfsVersion != TargetStub->IfsVersion) { if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) { WithColor::error() << "Interface Stub: IfsVersion Mismatch." << "\nFilenames: " << PreviousInputFilePath << " " << InputFilePath << "\nIfsVersion Values: " << Stub.IfsVersion << " " << TargetStub->IfsVersion << "\n"; return -1; } if (TargetStub->IfsVersion > Stub.IfsVersion) Stub.IfsVersion = TargetStub->IfsVersion; } if (Stub.Target != TargetStub->Target && !TargetStub->Target.empty()) { WithColor::error() << "Interface Stub: Target Mismatch." << "\nFilenames: " << PreviousInputFilePath << " " << InputFilePath; return -1; } if (Stub.SoName != TargetStub->SoName) { WithColor::error() << "Interface Stub: SoName Mismatch." << "\nFilenames: " << PreviousInputFilePath << " " << InputFilePath << "\nSoName Values: " << Stub.SoName << " " << TargetStub->SoName << "\n"; return -1; } if (Stub.NeededLibs != TargetStub->NeededLibs) { WithColor::error() << "Interface Stub: NeededLibs Mismatch." << "\nFilenames: " << PreviousInputFilePath << " " << InputFilePath << "\n"; return -1; } } for (auto Symbol : TargetStub->Symbols) { auto SI = SymbolMap.find(Symbol.Name); if (SI == SymbolMap.end()) { SymbolMap.insert( std::pair(Symbol.Name, Symbol)); continue; } assert(Symbol.Name == SI->second.Name && "Symbol Names Must Match."); // Check conflicts: if (Symbol.Type != SI->second.Type) { WithColor::error() << "Interface Stub: Type Mismatch for " << Symbol.Name << ".\nFilename: " << InputFilePath << "\nType Values: " << getTypeName(SI->second.Type) << " " << getTypeName(Symbol.Type) << "\n"; return -1; } if (Symbol.Size != SI->second.Size) { WithColor::error() << "Interface Stub: Size Mismatch for " << Symbol.Name << ".\nFilename: " << InputFilePath << "\nSize Values: " << SI->second.Size << " " << Symbol.Size << "\n"; return -1; } if (Symbol.Weak != SI->second.Weak) { Symbol.Weak = false; continue; } // TODO: Not checking Warning. Will be dropped. } PreviousInputFilePath = InputFilePath; } if (Stub.IfsVersion != IfsVersionCurrent) if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) { WithColor::error() << "Interface Stub: Bad IfsVersion: " << Stub.IfsVersion << ", llvm-ifs supported version: " << IfsVersionCurrent << ".\n"; return -1; } for (auto &Entry : SymbolMap) Stub.Symbols.push_back(Entry.second); // Change SoName before emitting stubs. if (Config.SoName) Stub.SoName = *Config.SoName; Error OverrideError = overrideIFSTarget(Stub, Config.OverrideArch, Config.OverrideEndianness, Config.OverrideBitWidth, Config.OptTargetTriple); if (OverrideError) fatalError(std::move(OverrideError)); if (Config.StripNeeded) Stub.NeededLibs.clear(); if (Error E = filterIFSSyms(Stub, Config.StripUndefined, Config.Exclude)) fatalError(std::move(E)); if (Config.StripSize) for (IFSSymbol &Sym : Stub.Symbols) Sym.Size.reset(); if (!Config.OutputElf && !Config.OutputIfs && !Config.OutputTbd) { if (!Config.OutputFormat) { WithColor::error() << "at least one output should be specified."; return -1; } } else if (Config.OutputFormat) { WithColor::error() << "'--output-format' cannot be used with " "'--output-{FILE_FORMAT}' options at the same time"; return -1; } if (Config.OutputFormat) { // TODO: Remove OutputFormat flag in the next revision. WithColor::warning() << "--output-format option is deprecated, please use " "--output-{FILE_FORMAT} options instead\n"; switch (*Config.OutputFormat) { case FileFormat::TBD: { std::error_code SysErr; raw_fd_ostream Out(*Config.Output, SysErr); if (SysErr) { WithColor::error() << "Couldn't open " << *Config.Output << " for writing.\n"; return -1; } if (!Stub.Target.Triple) { WithColor::error() << "Triple should be defined when output format is TBD"; return -1; } return writeTbdStub(llvm::Triple(*Stub.Target.Triple), Stub.Symbols, "TBD", Out); } case FileFormat::IFS: { Stub.IfsVersion = IfsVersionCurrent; if (*Config.InputFormat == FileFormat::ELF && Config.HintIfsTarget) { std::error_code HintEC(1, std::generic_category()); IFSTarget HintTarget = parseTriple(*Config.HintIfsTarget); if (*Stub.Target.Arch != *HintTarget.Arch) fatalError(make_error( "Triple hint does not match the actual architecture", HintEC)); if (*Stub.Target.Endianness != *HintTarget.Endianness) fatalError(make_error( "Triple hint does not match the actual endianness", HintEC)); if (*Stub.Target.BitWidth != *HintTarget.BitWidth) fatalError(make_error( "Triple hint does not match the actual bit width", HintEC)); stripIFSTarget(Stub, true, false, false, false); Stub.Target.Triple = *Config.HintIfsTarget; } else { stripIFSTarget(Stub, Config.StripIfsTarget, Config.StripIfsArch, Config.StripIfsEndianness, Config.StripIfsBitwidth); } Error IFSWriteError = writeIFS(*Config.Output, Stub, Config.WriteIfChanged); if (IFSWriteError) fatalError(std::move(IFSWriteError)); break; } case FileFormat::ELF: { Error TargetError = validateIFSTarget(Stub, true); if (TargetError) fatalError(std::move(TargetError)); Error BinaryWriteError = writeBinaryStub(*Config.Output, Stub, Config.WriteIfChanged); if (BinaryWriteError) fatalError(std::move(BinaryWriteError)); break; } } } else { // Check if output path for individual format. if (Config.OutputElf) { Error TargetError = validateIFSTarget(Stub, true); if (TargetError) fatalError(std::move(TargetError)); Error BinaryWriteError = writeBinaryStub(*Config.OutputElf, Stub, Config.WriteIfChanged); if (BinaryWriteError) fatalError(std::move(BinaryWriteError)); } if (Config.OutputIfs) { Stub.IfsVersion = IfsVersionCurrent; if (*Config.InputFormat == FileFormat::ELF && Config.HintIfsTarget) { std::error_code HintEC(1, std::generic_category()); IFSTarget HintTarget = parseTriple(*Config.HintIfsTarget); if (*Stub.Target.Arch != *HintTarget.Arch) fatalError(make_error( "Triple hint does not match the actual architecture", HintEC)); if (*Stub.Target.Endianness != *HintTarget.Endianness) fatalError(make_error( "Triple hint does not match the actual endianness", HintEC)); if (*Stub.Target.BitWidth != *HintTarget.BitWidth) fatalError(make_error( "Triple hint does not match the actual bit width", HintEC)); stripIFSTarget(Stub, true, false, false, false); Stub.Target.Triple = *Config.HintIfsTarget; } else { stripIFSTarget(Stub, Config.StripIfsTarget, Config.StripIfsArch, Config.StripIfsEndianness, Config.StripIfsBitwidth); } Error IFSWriteError = writeIFS(*Config.OutputIfs, Stub, Config.WriteIfChanged); if (IFSWriteError) fatalError(std::move(IFSWriteError)); } if (Config.OutputTbd) { std::error_code SysErr; raw_fd_ostream Out(*Config.OutputTbd, SysErr); if (SysErr) { WithColor::error() << "Couldn't open " << *Config.OutputTbd << " for writing.\n"; return -1; } if (!Stub.Target.Triple) { WithColor::error() << "Triple should be defined when output format is TBD"; return -1; } return writeTbdStub(llvm::Triple(*Stub.Target.Triple), Stub.Symbols, "TBD", Out); } } return 0; }