//===- llvm-readobj.cpp - Dump contents of an Object File -----------------===// // // 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 // //===----------------------------------------------------------------------===// // // This is a tool similar to readelf, except it works on multiple object file // formats. The main purpose of this tool is to provide detailed output suitable // for FileCheck. // // Flags should be similar to readelf where supported, but the output format // does not need to be identical. The point is to not make users learn yet // another set of flags. // // Output should be specialized for each format where appropriate. // //===----------------------------------------------------------------------===// #include "llvm-readobj.h" #include "ObjDumper.h" #include "WindowsResourceDumper.h" #include "llvm/DebugInfo/CodeView/GlobalTypeTableBuilder.h" #include "llvm/DebugInfo/CodeView/MergingTypeTableBuilder.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Object/Archive.h" #include "llvm/Object/COFFImportFile.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/MachOUniversal.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Object/Wasm.h" #include "llvm/Object/WindowsResource.h" #include "llvm/Object/XCOFFObjectFile.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/DataTypes.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/WithColor.h" using namespace llvm; using namespace llvm::object; namespace { using namespace llvm::opt; // for HelpHidden in Opts.inc 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) const char *const NAME[] = VALUE; #include "Opts.inc" #undef PREFIX const 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 ReadobjOptTable : public opt::OptTable { public: ReadobjOptTable() : OptTable(InfoTable) { setGroupedShortOptions(true); } }; enum OutputFormatTy { bsd, sysv, posix, darwin, just_symbols }; } // namespace namespace opts { static bool Addrsig; static bool All; static bool ArchSpecificInfo; static bool BBAddrMap; bool ExpandRelocs; static bool CGProfile; bool Demangle; static bool DependentLibraries; static bool DynRelocs; static bool DynamicSymbols; static bool FileHeaders; static bool Headers; static std::vector HexDump; static bool PrettyPrint; static bool PrintStackMap; static bool PrintStackSizes; static bool Relocations; bool SectionData; static bool SectionDetails; static bool SectionHeaders; bool SectionRelocations; bool SectionSymbols; static std::vector StringDump; static bool StringTable; static bool Symbols; static bool UnwindInfo; static cl::boolOrDefault SectionMapping; // ELF specific options. static bool DynamicTable; static bool ELFLinkerOptions; static bool GnuHashTable; static bool HashSymbols; static bool HashTable; static bool HashHistogram; static bool NeededLibraries; static bool Notes; static bool ProgramHeaders; bool RawRelr; static bool SectionGroups; static bool VersionInfo; // Mach-O specific options. static bool MachODataInCode; static bool MachODysymtab; static bool MachOIndirectSymbols; static bool MachOLinkerOptions; static bool MachOSegment; static bool MachOVersionMin; // PE/COFF specific options. static bool CodeView; static bool CodeViewEnableGHash; static bool CodeViewMergedTypes; bool CodeViewSubsectionBytes; static bool COFFBaseRelocs; static bool COFFDebugDirectory; static bool COFFDirectives; static bool COFFExports; static bool COFFImports; static bool COFFLoadConfig; static bool COFFResources; static bool COFFTLSDirectory; // XCOFF specific options. static bool XCOFFAuxiliaryHeader; OutputStyleTy Output = OutputStyleTy::LLVM; static std::vector InputFilenames; } // namespace opts static StringRef ToolName; namespace llvm { [[noreturn]] static void error(Twine Msg) { // Flush the standard output to print the error at a // proper place. fouts().flush(); WithColor::error(errs(), ToolName) << Msg << "\n"; exit(1); } [[noreturn]] void reportError(Error Err, StringRef Input) { assert(Err); if (Input == "-") Input = ""; handleAllErrors(createFileError(Input, std::move(Err)), [&](const ErrorInfoBase &EI) { error(EI.message()); }); llvm_unreachable("error() call should never return"); } void reportWarning(Error Err, StringRef Input) { assert(Err); if (Input == "-") Input = ""; // Flush the standard output to print the warning at a // proper place. fouts().flush(); handleAllErrors( createFileError(Input, std::move(Err)), [&](const ErrorInfoBase &EI) { WithColor::warning(errs(), ToolName) << EI.message() << "\n"; }); } } // namespace llvm static void parseOptions(const opt::InputArgList &Args) { opts::Addrsig = Args.hasArg(OPT_addrsig); opts::All = Args.hasArg(OPT_all); opts::ArchSpecificInfo = Args.hasArg(OPT_arch_specific); opts::BBAddrMap = Args.hasArg(OPT_bb_addr_map); opts::CGProfile = Args.hasArg(OPT_cg_profile); opts::Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, false); opts::DependentLibraries = Args.hasArg(OPT_dependent_libraries); opts::DynRelocs = Args.hasArg(OPT_dyn_relocations); opts::DynamicSymbols = Args.hasArg(OPT_dyn_syms); opts::ExpandRelocs = Args.hasArg(OPT_expand_relocs); opts::FileHeaders = Args.hasArg(OPT_file_header); opts::Headers = Args.hasArg(OPT_headers); opts::HexDump = Args.getAllArgValues(OPT_hex_dump_EQ); opts::Relocations = Args.hasArg(OPT_relocs); opts::SectionData = Args.hasArg(OPT_section_data); opts::SectionDetails = Args.hasArg(OPT_section_details); opts::SectionHeaders = Args.hasArg(OPT_section_headers); opts::SectionRelocations = Args.hasArg(OPT_section_relocations); opts::SectionSymbols = Args.hasArg(OPT_section_symbols); if (Args.hasArg(OPT_section_mapping)) opts::SectionMapping = cl::BOU_TRUE; else if (Args.hasArg(OPT_section_mapping_EQ_false)) opts::SectionMapping = cl::BOU_FALSE; else opts::SectionMapping = cl::BOU_UNSET; opts::PrintStackSizes = Args.hasArg(OPT_stack_sizes); opts::PrintStackMap = Args.hasArg(OPT_stackmap); opts::StringDump = Args.getAllArgValues(OPT_string_dump_EQ); opts::StringTable = Args.hasArg(OPT_string_table); opts::Symbols = Args.hasArg(OPT_symbols); opts::UnwindInfo = Args.hasArg(OPT_unwind); // ELF specific options. opts::DynamicTable = Args.hasArg(OPT_dynamic_table); opts::ELFLinkerOptions = Args.hasArg(OPT_elf_linker_options); if (Arg *A = Args.getLastArg(OPT_elf_output_style_EQ)) { std::string OutputStyleChoice = A->getValue(); opts::Output = StringSwitch(OutputStyleChoice) .Case("LLVM", opts::OutputStyleTy::LLVM) .Case("GNU", opts::OutputStyleTy::GNU) .Case("JSON", opts::OutputStyleTy::JSON) .Default(opts::OutputStyleTy::UNKNOWN); if (opts::Output == opts::OutputStyleTy::UNKNOWN) { error("--elf-output-style value should be either 'LLVM', 'GNU', or " "'JSON', but was '" + OutputStyleChoice + "'"); } } opts::GnuHashTable = Args.hasArg(OPT_gnu_hash_table); opts::HashSymbols = Args.hasArg(OPT_hash_symbols); opts::HashTable = Args.hasArg(OPT_hash_table); opts::HashHistogram = Args.hasArg(OPT_histogram); opts::NeededLibraries = Args.hasArg(OPT_needed_libs); opts::Notes = Args.hasArg(OPT_notes); opts::PrettyPrint = Args.hasArg(OPT_pretty_print); opts::ProgramHeaders = Args.hasArg(OPT_program_headers); opts::RawRelr = Args.hasArg(OPT_raw_relr); opts::SectionGroups = Args.hasArg(OPT_section_groups); opts::VersionInfo = Args.hasArg(OPT_version_info); // Mach-O specific options. opts::MachODataInCode = Args.hasArg(OPT_macho_data_in_code); opts::MachODysymtab = Args.hasArg(OPT_macho_dysymtab); opts::MachOIndirectSymbols = Args.hasArg(OPT_macho_indirect_symbols); opts::MachOLinkerOptions = Args.hasArg(OPT_macho_linker_options); opts::MachOSegment = Args.hasArg(OPT_macho_segment); opts::MachOVersionMin = Args.hasArg(OPT_macho_version_min); // PE/COFF specific options. opts::CodeView = Args.hasArg(OPT_codeview); opts::CodeViewEnableGHash = Args.hasArg(OPT_codeview_ghash); opts::CodeViewMergedTypes = Args.hasArg(OPT_codeview_merged_types); opts::CodeViewSubsectionBytes = Args.hasArg(OPT_codeview_subsection_bytes); opts::COFFBaseRelocs = Args.hasArg(OPT_coff_basereloc); opts::COFFDebugDirectory = Args.hasArg(OPT_coff_debug_directory); opts::COFFDirectives = Args.hasArg(OPT_coff_directives); opts::COFFExports = Args.hasArg(OPT_coff_exports); opts::COFFImports = Args.hasArg(OPT_coff_imports); opts::COFFLoadConfig = Args.hasArg(OPT_coff_load_config); opts::COFFResources = Args.hasArg(OPT_coff_resources); opts::COFFTLSDirectory = Args.hasArg(OPT_coff_tls_directory); // XCOFF specific options. opts::XCOFFAuxiliaryHeader = Args.hasArg(OPT_auxiliary_header); opts::InputFilenames = Args.getAllArgValues(OPT_INPUT); } namespace { struct ReadObjTypeTableBuilder { ReadObjTypeTableBuilder() : IDTable(Allocator), TypeTable(Allocator), GlobalIDTable(Allocator), GlobalTypeTable(Allocator) {} llvm::BumpPtrAllocator Allocator; llvm::codeview::MergingTypeTableBuilder IDTable; llvm::codeview::MergingTypeTableBuilder TypeTable; llvm::codeview::GlobalTypeTableBuilder GlobalIDTable; llvm::codeview::GlobalTypeTableBuilder GlobalTypeTable; std::vector> Binaries; }; } // namespace static ReadObjTypeTableBuilder CVTypes; /// Creates an format-specific object file dumper. static Expected> createDumper(const ObjectFile &Obj, ScopedPrinter &Writer) { if (const COFFObjectFile *COFFObj = dyn_cast(&Obj)) return createCOFFDumper(*COFFObj, Writer); if (const ELFObjectFileBase *ELFObj = dyn_cast(&Obj)) return createELFDumper(*ELFObj, Writer); if (const MachOObjectFile *MachOObj = dyn_cast(&Obj)) return createMachODumper(*MachOObj, Writer); if (const WasmObjectFile *WasmObj = dyn_cast(&Obj)) return createWasmDumper(*WasmObj, Writer); if (const XCOFFObjectFile *XObj = dyn_cast(&Obj)) return createXCOFFDumper(*XObj, Writer); return createStringError(errc::invalid_argument, "unsupported object file format"); } /// Dumps the specified object file. static void dumpObject(ObjectFile &Obj, ScopedPrinter &Writer, const Archive *A = nullptr) { std::string FileStr = A ? Twine(A->getFileName() + "(" + Obj.getFileName() + ")").str() : Obj.getFileName().str(); std::string ContentErrString; if (Error ContentErr = Obj.initContent()) ContentErrString = "unable to continue dumping, the file is corrupt: " + toString(std::move(ContentErr)); ObjDumper *Dumper; Expected> DumperOrErr = createDumper(Obj, Writer); if (!DumperOrErr) reportError(DumperOrErr.takeError(), FileStr); Dumper = (*DumperOrErr).get(); Dumper->printFileSummary(FileStr, Obj, opts::InputFilenames, A); if (opts::FileHeaders) Dumper->printFileHeaders(); if (Obj.isXCOFF() && opts::XCOFFAuxiliaryHeader) Dumper->printAuxiliaryHeader(); // This is only used for ELF currently. In some cases, when an object is // corrupt (e.g. truncated), we can't dump anything except the file header. if (!ContentErrString.empty()) reportError(createError(ContentErrString), FileStr); if (opts::SectionDetails || opts::SectionHeaders) { if (opts::Output == opts::GNU && opts::SectionDetails) Dumper->printSectionDetails(); else Dumper->printSectionHeaders(); } if (opts::HashSymbols) Dumper->printHashSymbols(); if (opts::ProgramHeaders || opts::SectionMapping == cl::BOU_TRUE) Dumper->printProgramHeaders(opts::ProgramHeaders, opts::SectionMapping); if (opts::DynamicTable) Dumper->printDynamicTable(); if (opts::NeededLibraries) Dumper->printNeededLibraries(); if (opts::Relocations) Dumper->printRelocations(); if (opts::DynRelocs) Dumper->printDynamicRelocations(); if (opts::UnwindInfo) Dumper->printUnwindInfo(); if (opts::Symbols || opts::DynamicSymbols) Dumper->printSymbols(opts::Symbols, opts::DynamicSymbols); if (!opts::StringDump.empty()) Dumper->printSectionsAsString(Obj, opts::StringDump); if (!opts::HexDump.empty()) Dumper->printSectionsAsHex(Obj, opts::HexDump); if (opts::HashTable) Dumper->printHashTable(); if (opts::GnuHashTable) Dumper->printGnuHashTable(); if (opts::VersionInfo) Dumper->printVersionInfo(); if (opts::StringTable) Dumper->printStringTable(); if (Obj.isELF()) { if (opts::DependentLibraries) Dumper->printDependentLibs(); if (opts::ELFLinkerOptions) Dumper->printELFLinkerOptions(); if (opts::ArchSpecificInfo) Dumper->printArchSpecificInfo(); if (opts::SectionGroups) Dumper->printGroupSections(); if (opts::HashHistogram) Dumper->printHashHistograms(); if (opts::CGProfile) Dumper->printCGProfile(); if (opts::BBAddrMap) Dumper->printBBAddrMaps(); if (opts::Addrsig) Dumper->printAddrsig(); if (opts::Notes) Dumper->printNotes(); } if (Obj.isCOFF()) { if (opts::COFFImports) Dumper->printCOFFImports(); if (opts::COFFExports) Dumper->printCOFFExports(); if (opts::COFFDirectives) Dumper->printCOFFDirectives(); if (opts::COFFBaseRelocs) Dumper->printCOFFBaseReloc(); if (opts::COFFDebugDirectory) Dumper->printCOFFDebugDirectory(); if (opts::COFFTLSDirectory) Dumper->printCOFFTLSDirectory(); if (opts::COFFResources) Dumper->printCOFFResources(); if (opts::COFFLoadConfig) Dumper->printCOFFLoadConfig(); if (opts::CGProfile) Dumper->printCGProfile(); if (opts::Addrsig) Dumper->printAddrsig(); if (opts::CodeView) Dumper->printCodeViewDebugInfo(); if (opts::CodeViewMergedTypes) Dumper->mergeCodeViewTypes(CVTypes.IDTable, CVTypes.TypeTable, CVTypes.GlobalIDTable, CVTypes.GlobalTypeTable, opts::CodeViewEnableGHash); } if (Obj.isMachO()) { if (opts::MachODataInCode) Dumper->printMachODataInCode(); if (opts::MachOIndirectSymbols) Dumper->printMachOIndirectSymbols(); if (opts::MachOLinkerOptions) Dumper->printMachOLinkerOptions(); if (opts::MachOSegment) Dumper->printMachOSegment(); if (opts::MachOVersionMin) Dumper->printMachOVersionMin(); if (opts::MachODysymtab) Dumper->printMachODysymtab(); if (opts::CGProfile) Dumper->printCGProfile(); } if (opts::PrintStackMap) Dumper->printStackMap(); if (opts::PrintStackSizes) Dumper->printStackSizes(); } /// Dumps each object file in \a Arc; static void dumpArchive(const Archive *Arc, ScopedPrinter &Writer) { Error Err = Error::success(); for (auto &Child : Arc->children(Err)) { Expected> ChildOrErr = Child.getAsBinary(); if (!ChildOrErr) { if (auto E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError())) reportError(std::move(E), Arc->getFileName()); continue; } Binary *Bin = ChildOrErr->get(); if (ObjectFile *Obj = dyn_cast(Bin)) dumpObject(*Obj, Writer, Arc); else if (COFFImportFile *Imp = dyn_cast(Bin)) dumpCOFFImportFile(Imp, Writer); else reportWarning(createStringError(errc::invalid_argument, Bin->getFileName() + " has an unsupported file type"), Arc->getFileName()); } if (Err) reportError(std::move(Err), Arc->getFileName()); } /// Dumps each object file in \a MachO Universal Binary; static void dumpMachOUniversalBinary(const MachOUniversalBinary *UBinary, ScopedPrinter &Writer) { for (const MachOUniversalBinary::ObjectForArch &Obj : UBinary->objects()) { Expected> ObjOrErr = Obj.getAsObjectFile(); if (ObjOrErr) dumpObject(*ObjOrErr.get(), Writer); else if (auto E = isNotObjectErrorInvalidFileType(ObjOrErr.takeError())) reportError(ObjOrErr.takeError(), UBinary->getFileName()); else if (Expected> AOrErr = Obj.getAsArchive()) dumpArchive(&*AOrErr.get(), Writer); } } /// Dumps \a WinRes, Windows Resource (.res) file; static void dumpWindowsResourceFile(WindowsResource *WinRes, ScopedPrinter &Printer) { WindowsRes::Dumper Dumper(WinRes, Printer); if (auto Err = Dumper.printData()) reportError(std::move(Err), WinRes->getFileName()); } /// Opens \a File and dumps it. static void dumpInput(StringRef File, ScopedPrinter &Writer) { ErrorOr> FileOrErr = MemoryBuffer::getFileOrSTDIN(File, /*IsText=*/false, /*RequiresNullTerminator=*/false); if (std::error_code EC = FileOrErr.getError()) return reportError(errorCodeToError(EC), File); std::unique_ptr &Buffer = FileOrErr.get(); file_magic Type = identify_magic(Buffer->getBuffer()); if (Type == file_magic::bitcode) { reportWarning(createStringError(errc::invalid_argument, "bitcode files are not supported"), File); return; } Expected> BinaryOrErr = createBinary( Buffer->getMemBufferRef(), /*Context=*/nullptr, /*InitContent=*/false); if (!BinaryOrErr) reportError(BinaryOrErr.takeError(), File); std::unique_ptr Bin = std::move(*BinaryOrErr); if (Archive *Arc = dyn_cast(Bin.get())) dumpArchive(Arc, Writer); else if (MachOUniversalBinary *UBinary = dyn_cast(Bin.get())) dumpMachOUniversalBinary(UBinary, Writer); else if (ObjectFile *Obj = dyn_cast(Bin.get())) dumpObject(*Obj, Writer); else if (COFFImportFile *Import = dyn_cast(Bin.get())) dumpCOFFImportFile(Import, Writer); else if (WindowsResource *WinRes = dyn_cast(Bin.get())) dumpWindowsResourceFile(WinRes, Writer); else llvm_unreachable("unrecognized file type"); CVTypes.Binaries.push_back( OwningBinary(std::move(Bin), std::move(Buffer))); } std::unique_ptr createWriter() { if (opts::Output == opts::JSON) return std::make_unique( fouts(), opts::PrettyPrint ? 2 : 0, std::make_unique()); return std::make_unique(fouts()); } int main(int argc, char *argv[]) { InitLLVM X(argc, argv); BumpPtrAllocator A; StringSaver Saver(A); ReadobjOptTable Tbl; ToolName = argv[0]; opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { error(Msg); exit(1); }); if (Args.hasArg(OPT_help)) { Tbl.printHelp( outs(), (Twine(ToolName) + " [options] ").str().c_str(), "LLVM Object Reader"); // TODO Replace this with OptTable API once it adds extrahelp support. outs() << "\nPass @FILE as argument to read options from FILE.\n"; return 0; } if (Args.hasArg(OPT_version)) { cl::PrintVersionMessage(); return 0; } if (sys::path::stem(argv[0]).contains("readelf")) opts::Output = opts::GNU; parseOptions(Args); // Default to print error if no filename is specified. if (opts::InputFilenames.empty()) { error("no input files specified"); } if (opts::All) { opts::FileHeaders = true; opts::XCOFFAuxiliaryHeader = true; opts::ProgramHeaders = true; opts::SectionHeaders = true; opts::Symbols = true; opts::Relocations = true; opts::DynamicTable = true; opts::Notes = true; opts::VersionInfo = true; opts::UnwindInfo = true; opts::SectionGroups = true; opts::HashHistogram = true; if (opts::Output == opts::LLVM) { opts::Addrsig = true; opts::PrintStackSizes = true; } } if (opts::Headers) { opts::FileHeaders = true; opts::XCOFFAuxiliaryHeader = true; opts::ProgramHeaders = true; opts::SectionHeaders = true; } std::unique_ptr Writer = createWriter(); for (const std::string &I : opts::InputFilenames) dumpInput(I, *Writer.get()); if (opts::CodeViewMergedTypes) { if (opts::CodeViewEnableGHash) dumpCodeViewMergedTypes(*Writer.get(), CVTypes.GlobalIDTable.records(), CVTypes.GlobalTypeTable.records()); else dumpCodeViewMergedTypes(*Writer.get(), CVTypes.IDTable.records(), CVTypes.TypeTable.records()); } return 0; }