diff --git a/llvm/docs/CommandGuide/llvm-readelf.rst b/llvm/docs/CommandGuide/llvm-readelf.rst --- a/llvm/docs/CommandGuide/llvm-readelf.rst +++ b/llvm/docs/CommandGuide/llvm-readelf.rst @@ -72,9 +72,10 @@ .. option:: --elf-output-style= - Format ELF information in the specified style. Valid options are ``LLVM`` and - ``GNU``. ``LLVM`` output is an expanded and structured format, whilst ``GNU`` - (the default) output mimics the equivalent GNU :program:`readelf` output. + Format ELF information in the specified style. Valid options are ``LLVM``, + ``GNU``, and ``JSON``. ``LLVM`` output is an expanded and structured format. + ``GNU`` (the default) output mimics the equivalent GNU :program:`readelf` + output. ``JSON`` is JSON formatted output intended for machine consumption. .. option:: --section-groups, -g @@ -127,6 +128,11 @@ Display all notes. +.. option:: --pretty-print + + When used with :option:`--elf-output-style`, JSON output will be formatted in + a more readable format. + .. option:: --program-headers, --segments, -l Display the program headers. diff --git a/llvm/docs/CommandGuide/llvm-readobj.rst b/llvm/docs/CommandGuide/llvm-readobj.rst --- a/llvm/docs/CommandGuide/llvm-readobj.rst +++ b/llvm/docs/CommandGuide/llvm-readobj.rst @@ -183,9 +183,10 @@ .. option:: --elf-output-style= - Format ELF information in the specified style. Valid options are ``LLVM`` and - ``GNU``. ``LLVM`` output (the default) is an expanded and structured format, - whilst ``GNU`` output mimics the equivalent GNU :program:`readelf` output. + Format ELF information in the specified style. Valid options are ``LLVM``, + ``GNU``, and ``JSON``. ``LLVM`` output (the default) is an expanded and + structured format. ``GNU`` output mimics the equivalent GNU :program:`readelf` + output. ``JSON`` is JSON formatted output intended for machine consumption. .. option:: --section-groups, -g @@ -207,6 +208,11 @@ Display all notes. +.. option:: --pretty-print + + When used with :option:`--elf-output-style`, JSON output will be formatted in + a more readable format. + .. option:: --program-headers, --segments, -l Display the program headers. diff --git a/llvm/test/tools/llvm-readobj/ELF/file-summary-json.test b/llvm/test/tools/llvm-readobj/ELF/file-summary-json.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-readobj/ELF/file-summary-json.test @@ -0,0 +1,114 @@ +## Test how we output JSON file summaries. + +# RUN: rm -rf %t.dir +# RUN: mkdir -p %t.dir +# RUN: yaml2obj %s -o %t.dir/obj + +## Test outputting file summary for a single file. +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.dir/obj | \ +# RUN: FileCheck %s --check-prefix=SINGLE \ +# RUN: --match-full-lines --strict-whitespace --implicit-check-not={{.}} + +# SINGLE:[ +# SINGLE-NEXT: { +# SINGLE-NEXT: "{{.*}}/obj": { +# SINGLE-NEXT: "FileSummary": { +# SINGLE-NEXT: "File": "{{.*}}/obj", +# SINGLE-NEXT: "Format": "elf64-x86-64", +# SINGLE-NEXT: "Arch": "x86_64", +# SINGLE-NEXT: "AddressSize": "64bit", +# SINGLE-NEXT: "LoadName": "" +# SINGLE-NEXT: } +# SINGLE-NEXT: } +# SINGLE-NEXT: } +# SINGLE-NEXT:] + +## Test outputting file summary for multiple files. +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.dir/obj %t.dir/obj | \ +# RUN: FileCheck %s --check-prefix=MULTI \ +# RUN: --match-full-lines --strict-whitespace --implicit-check-not={{.}} + +# MULTI:[ +# MULTI-NEXT: { +# MULTI-NEXT: "{{.*}}/obj": { +# MULTI-NEXT: "FileSummary": { +# MULTI-NEXT: "File": "{{.*}}/obj", +# MULTI-NEXT: "Format": "elf64-x86-64", +# MULTI-NEXT: "Arch": "x86_64", +# MULTI-NEXT: "AddressSize": "64bit", +# MULTI-NEXT: "LoadName": "" +# MULTI-NEXT: } +# MULTI-NEXT: } +# MULTI-NEXT: }, +# MULTI-NEXT: { +# MULTI-NEXT: "{{.*}}/obj": { +# MULTI-NEXT: "FileSummary": { +# MULTI-NEXT: "File": "{{.*}}/obj", +# MULTI-NEXT: "Format": "elf64-x86-64", +# MULTI-NEXT: "Arch": "x86_64", +# MULTI-NEXT: "AddressSize": "64bit", +# MULTI-NEXT: "LoadName": "" +# MULTI-NEXT: } +# MULTI-NEXT: } +# MULTI-NEXT: } +# MULTI-NEXT:] + +## Test outputting file summary for an archive with a single file. +# RUN: rm -f %t.archive-single +# RUN: llvm-ar rc %t.archive-single %t.dir/obj +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.archive-single | \ +# RUN: FileCheck %s --check-prefix=ARCH-SINGLE \ +# RUN: --match-full-lines --strict-whitespace --implicit-check-not={{.}} + +# ARCH-SINGLE:[ +# ARCH-SINGLE-NEXT: { +# ARCH-SINGLE-NEXT: "{{.*}}.archive-single(obj)": { +# ARCH-SINGLE-NEXT: "FileSummary": { +# ARCH-SINGLE-NEXT: "File": "{{.*}}.archive-single(obj)", +# ARCH-SINGLE-NEXT: "Format": "elf64-x86-64", +# ARCH-SINGLE-NEXT: "Arch": "x86_64", +# ARCH-SINGLE-NEXT: "AddressSize": "64bit", +# ARCH-SINGLE-NEXT: "LoadName": "" +# ARCH-SINGLE-NEXT: } +# ARCH-SINGLE-NEXT: } +# ARCH-SINGLE-NEXT: } +# ARCH-SINGLE-NEXT:] + +## Test outputting file summary for an archive with multiple files. +# RUN: rm -f %t.archive-multiple +# RUN: llvm-ar rc %t.archive-multiple %t.dir/obj %t.dir/obj +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.archive-multiple | \ +# RUN: FileCheck %s --check-prefix=ARCH-MULTI \ +# RUN: --match-full-lines --strict-whitespace --implicit-check-not={{.}} + +# ARCH-MULTI:[ +# ARCH-MULTI-NEXT: { +# ARCH-MULTI-NEXT: "{{.*}}.archive-multiple(obj)": { +# ARCH-MULTI-NEXT: "FileSummary": { +# ARCH-MULTI-NEXT: "File": "{{.*}}.archive-multiple(obj)", +# ARCH-MULTI-NEXT: "Format": "elf64-x86-64", +# ARCH-MULTI-NEXT: "Arch": "x86_64", +# ARCH-MULTI-NEXT: "AddressSize": "64bit", +# ARCH-MULTI-NEXT: "LoadName": "" +# ARCH-MULTI-NEXT: } +# ARCH-MULTI-NEXT: } +# ARCH-MULTI-NEXT: }, +# ARCH-MULTI-NEXT: { +# ARCH-MULTI-NEXT: "{{.*}}.archive-multiple(obj)": { +# ARCH-MULTI-NEXT: "FileSummary": { +# ARCH-MULTI-NEXT: "File": "{{.*}}.archive-multiple(obj)", +# ARCH-MULTI-NEXT: "Format": "elf64-x86-64", +# ARCH-MULTI-NEXT: "Arch": "x86_64", +# ARCH-MULTI-NEXT: "AddressSize": "64bit", +# ARCH-MULTI-NEXT: "LoadName": "" +# ARCH-MULTI-NEXT: } +# ARCH-MULTI-NEXT: } +# ARCH-MULTI-NEXT: } +# ARCH-MULTI-NEXT:] + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 diff --git a/llvm/test/tools/llvm-readobj/ELF/output-style.test b/llvm/test/tools/llvm-readobj/ELF/output-style.test --- a/llvm/test/tools/llvm-readobj/ELF/output-style.test +++ b/llvm/test/tools/llvm-readobj/ELF/output-style.test @@ -1,4 +1,4 @@ ## Error for an unknown output style. RUN: not llvm-readobj --elf-output-style=unknown 2>&1 | FileCheck %s -CHECK: error: --elf-output-style value should be either 'LLVM' or 'GNU' +CHECK: error: --elf-output-style value should be either 'LLVM', 'GNU', or 'JSON', but was 'unknown' diff --git a/llvm/test/tools/llvm-readobj/ELF/pretty-print.test b/llvm/test/tools/llvm-readobj/ELF/pretty-print.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-readobj/ELF/pretty-print.test @@ -0,0 +1,48 @@ +## Test the JSON pretty-print flag. +# +# RUN: yaml2obj %s -o %t.pretty + +## Test JSON with pretty-print off. +# RUN: llvm-readobj --elf-output-style=JSON %t.pretty | \ +# RUN: FileCheck %s --check-prefix=NO-PRETTY \ +# RUN: --strict-whitespace --implicit-check-not={{.}} + +# NO-PRETTY:[ +# NO-PRETTY-SAME:{ +# NO-PRETTY-SAME:"{{.*}}.pretty":{ +# NO-PRETTY-SAME:"FileSummary":{ +# NO-PRETTY-SAME:"File":"{{.*}}.pretty", +# NO-PRETTY-SAME:"Format":"elf64-x86-64", +# NO-PRETTY-SAME:"Arch":"x86_64", +# NO-PRETTY-SAME:"AddressSize":"64bit", +# NO-PRETTY-SAME:"LoadName":"" +# NO-PRETTY-SAME:} +# NO-PRETTY-SAME:} +# NO-PRETTY-SAME:} +# NO-PRETTY-SAME:] + +## Test JSON with pretty-print on. +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.pretty | \ +# RUN: FileCheck %s --check-prefix=PRETTY \ +# RUN: --match-full-lines --strict-whitespace --implicit-check-not={{.}} + +# PRETTY:[ +# PRETTY-NEXT: { +# PRETTY-NEXT: "{{.*}}.pretty": { +# PRETTY-NEXT: "FileSummary": { +# PRETTY-NEXT: "File": "{{.*}}.pretty", +# PRETTY-NEXT: "Format": "elf64-x86-64", +# PRETTY-NEXT: "Arch": "x86_64", +# PRETTY-NEXT: "AddressSize": "64bit", +# PRETTY-NEXT: "LoadName": "" +# PRETTY-NEXT: } +# PRETTY-NEXT: } +# PRETTY-NEXT: } +# PRETTY-NEXT:] + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -31,6 +31,7 @@ #include "llvm/BinaryFormat/AMDGPUMetadataVerifier.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/Demangle/Demangle.h" +#include "llvm/Object/Archive.h" #include "llvm/Object/ELF.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ELFTypes.h" @@ -548,6 +549,9 @@ assert(&this->W.getOStream() == &llvm::fouts()); } + void printFileSummary(StringRef FileStr, ObjectFile &Obj, + ArrayRef InputFilenames, + const Archive *A) override; void printFileHeaders() override; void printGroupSections() override; void printRelocations() override; @@ -697,9 +701,27 @@ void printMipsPLT(const MipsGOTParser &Parser) override; void printMipsABIFlags() override; +protected: ScopedPrinter &W; }; +// JSONELFDumper shares most of the same implementation as LLVMELFDumper except +// it uses a JSONScopedPrinter. +template class JSONELFDumper : public LLVMELFDumper { +public: + LLVM_ELF_IMPORT_TYPES_ELFT(ELFT) + + JSONELFDumper(const object::ELFObjectFile &ObjF, ScopedPrinter &Writer) + : LLVMELFDumper(ObjF, Writer) {} + + void printFileSummary(StringRef FileStr, ObjectFile &Obj, + ArrayRef InputFilenames, + const Archive *A) override; + +private: + std::unique_ptr FileScope; +}; + } // end anonymous namespace namespace llvm { @@ -709,6 +731,8 @@ createELFDumper(const ELFObjectFile &Obj, ScopedPrinter &Writer) { if (opts::Output == opts::GNU) return std::make_unique>(Obj, Writer); + else if (opts::Output == opts::JSON) + return std::make_unique>(Obj, Writer); return std::make_unique>(Obj, Writer); } @@ -3225,6 +3249,16 @@ return nullptr; } +template +void GNUELFDumper::printFileSummary(StringRef FileStr, ObjectFile &Obj, + ArrayRef InputFilenames, + const Archive *A) { + if (InputFilenames.size() > 1 || A) { + this->W.startLine() << "\n"; + this->W.printString("File", FileStr); + } +} + template void GNUELFDumper::printFileHeaders() { const Elf_Ehdr &e = this->Obj.getHeader(); OS << "ELF Header:\n"; @@ -7306,3 +7340,18 @@ W.printFlags("Flags 1", Flags->flags1, makeArrayRef(ElfMipsFlags1)); W.printHex("Flags 2", Flags->flags2); } + +template +void JSONELFDumper::printFileSummary(StringRef FileStr, ObjectFile &Obj, + ArrayRef InputFilenames, + const Archive *A) { + FileScope = std::make_unique(this->W, FileStr); + DictScope D(this->W, "FileSummary"); + this->W.printString("File", FileStr); + this->W.printString("Format", Obj.getFileFormatName()); + this->W.printString("Arch", Triple::getArchTypeName(Obj.getArch())); + this->W.printString( + "AddressSize", + std::string(formatv("{0}bit", 8 * Obj.getBytesInAddress()))); + this->printLoadName(); +} diff --git a/llvm/tools/llvm-readobj/ObjDumper.h b/llvm/tools/llvm-readobj/ObjDumper.h --- a/llvm/tools/llvm-readobj/ObjDumper.h +++ b/llvm/tools/llvm-readobj/ObjDumper.h @@ -20,6 +20,7 @@ namespace llvm { namespace object { +class Archive; class COFFImportFile; class ObjectFile; class XCOFFObjectFile; @@ -39,6 +40,9 @@ virtual bool canDumpContent() { return true; } + virtual void printFileSummary(StringRef FileStr, object::ObjectFile &Obj, + ArrayRef InputFilenames, + const object::Archive *A); virtual void printFileHeaders() = 0; virtual void printSectionHeaders() = 0; virtual void printRelocations() = 0; diff --git a/llvm/tools/llvm-readobj/ObjDumper.cpp b/llvm/tools/llvm-readobj/ObjDumper.cpp --- a/llvm/tools/llvm-readobj/ObjDumper.cpp +++ b/llvm/tools/llvm-readobj/ObjDumper.cpp @@ -13,6 +13,7 @@ #include "ObjDumper.h" #include "llvm-readobj.h" +#include "llvm/Object/Archive.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" @@ -85,6 +86,18 @@ } } +void ObjDumper::printFileSummary(StringRef FileStr, object::ObjectFile &Obj, + ArrayRef InputFilenames, + const object::Archive *A) { + W.startLine() << "\n"; + W.printString("File", FileStr); + W.printString("Format", Obj.getFileFormatName()); + W.printString("Arch", Triple::getArchTypeName(Obj.getArch())); + W.printString("AddressSize", + std::string(formatv("{0}bit", 8 * Obj.getBytesInAddress()))); + this->printLoadName(); +} + static std::vector getSectionRefsByNameOrIndex(const object::ObjectFile &Obj, ArrayRef Sections) { diff --git a/llvm/tools/llvm-readobj/Opts.td b/llvm/tools/llvm-readobj/Opts.td --- a/llvm/tools/llvm-readobj/Opts.td +++ b/llvm/tools/llvm-readobj/Opts.td @@ -28,6 +28,7 @@ def file_header : FF<"file-header", "Display file header">; def headers : FF<"headers", "Equivalent to setting: --file-header, --program-headers, --section-headers">; defm hex_dump : Eq<"hex-dump", "Display the specified section(s) as hexadecimal bytes">, MetaVarName<"">; +def pretty_print : FF<"pretty-print", "Pretty print JSON output">; def relocs : FF<"relocs", "Display the relocation entries in the file">; def section_data : FF<"section-data", "Display section data for each section shown. This option has no effect for GNU style output">; def section_details : FF<"section-details", "Display the section details">; @@ -47,7 +48,7 @@ def grp_elf : OptionGroup<"kind">, HelpText<"OPTIONS (ELF specific)">; def dynamic_table : FF<"dynamic-table", "Display the dynamic section table">, Group; def elf_linker_options : FF<"elf-linker-options", "Display the .linker-options section">, Group; -defm elf_output_style : Eq<"elf-output-style", "Specify ELF dump style: LLVM or GNU">, Group; +defm elf_output_style : Eq<"elf-output-style", "Specify ELF dump style: LLVM, GNU, JSON">, Group; def histogram : FF<"histogram", "Display bucket list histogram for hash sections">, Group; def section_groups : FF<"section-groups", "Display section groups">, Group; def gnu_hash_table : FF<"gnu-hash-table", "Display the GNU hash table for dynamic symbols">, Group; diff --git a/llvm/tools/llvm-readobj/llvm-readobj.h b/llvm/tools/llvm-readobj/llvm-readobj.h --- a/llvm/tools/llvm-readobj/llvm-readobj.h +++ b/llvm/tools/llvm-readobj/llvm-readobj.h @@ -39,7 +39,7 @@ extern bool RawRelr; extern bool CodeViewSubsectionBytes; extern bool Demangle; -enum OutputStyleTy { LLVM, GNU }; +enum OutputStyleTy { LLVM, GNU, JSON, UNKNOWN }; extern OutputStyleTy Output; } // namespace opts diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp --- a/llvm/tools/llvm-readobj/llvm-readobj.cpp +++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp @@ -99,6 +99,7 @@ static bool FileHeaders; static bool Headers; static std::vector HexDump; +static bool PrettyPrint; static bool PrintStackMap; static bool PrintStackSizes; static bool Relocations; @@ -230,13 +231,17 @@ 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)) { - StringRef V(A->getValue()); - if (V == "LLVM") - opts::Output = opts::OutputStyleTy::LLVM; - else if (V == "GNU") - opts::Output = opts::OutputStyleTy::GNU; - else - error("--elf-output-style value should be either 'LLVM' or 'GNU'"); + 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); @@ -244,6 +249,7 @@ 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); @@ -333,18 +339,7 @@ reportError(DumperOrErr.takeError(), FileStr); Dumper = (*DumperOrErr).get(); - if (opts::Output == opts::LLVM || opts::InputFilenames.size() > 1 || A) { - Writer.startLine() << "\n"; - Writer.printString("File", FileStr); - } - if (opts::Output == opts::LLVM) { - Writer.printString("Format", Obj.getFileFormatName()); - Writer.printString("Arch", Triple::getArchTypeName(Obj.getArch())); - Writer.printString( - "AddressSize", - std::string(formatv("{0}bit", 8 * Obj.getBytesInAddress()))); - Dumper->printLoadName(); - } + Dumper->printFileSummary(FileStr, Obj, opts::InputFilenames, A); if (opts::FileHeaders) Dumper->printFileHeaders(); @@ -550,6 +545,13 @@ 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; @@ -610,16 +612,17 @@ opts::SectionHeaders = true; } - ScopedPrinter Writer(fouts()); + std::unique_ptr Writer = createWriter(); + for (const std::string &I : opts::InputFilenames) - dumpInput(I, Writer); + dumpInput(I, *Writer.get()); if (opts::CodeViewMergedTypes) { if (opts::CodeViewEnableGHash) - dumpCodeViewMergedTypes(Writer, CVTypes.GlobalIDTable.records(), + dumpCodeViewMergedTypes(*Writer.get(), CVTypes.GlobalIDTable.records(), CVTypes.GlobalTypeTable.records()); else - dumpCodeViewMergedTypes(Writer, CVTypes.IDTable.records(), + dumpCodeViewMergedTypes(*Writer.get(), CVTypes.IDTable.records(), CVTypes.TypeTable.records()); }