Index: llvm/test/tools/llvm-readobj/ELF/json-file-summary.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-readobj/ELF/json-file-summary.test @@ -0,0 +1,72 @@ +## Test how we output JSON file summaries. + +## Test outputting file summary for a single file. +# RUN: yaml2obj %s -o %t.trivial.obj.elf-x86-64 +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.trivial.obj.elf-x86-64 | FileCheck %s -DFILE=%t.trivial.obj.elf-x86-64 --check-prefix=SINGLE + +# SINGLE: "FileSummary": { +# SINGLE-NEXT: "File": "[[FILE]]", +# SINGLE-NEXT: "Format": "elf64-x86-64", +# SINGLE-NEXT: "Arch": "x86_64", +# SINGLE-NEXT: "AddressSize": "64bit", +# SINGLE-NEXT: "LoadName": "" +# SINGLE-NEXT: } + +## Test outputting file summary for multiple files. +# RUN: yaml2obj %s -o %t.trivial.obj.elf-x86-64 +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.trivial.obj.elf-x86-64 %t.trivial.obj.elf-x86-64 | FileCheck %s -DFILE=%t.trivial.obj.elf-x86-64 --check-prefix=MULTI + +# MULTI: "FileSummary": { +# MULTI-NEXT: "File": "[[FILE]]", +# MULTI-NEXT: "Format": "elf64-x86-64", +# MULTI-NEXT: "Arch": "x86_64", +# MULTI-NEXT: "AddressSize": "64bit", +# MULTI-NEXT: "LoadName": "" +# MULTI-NEXT: } +# MULTI: "FileSummary": { +# MULTI-NEXT: "File": "{{.*}}trivial.obj.elf-x86-64", +# MULTI-NEXT: "Format": "elf64-x86-64", +# MULTI-NEXT: "Arch": "x86_64", +# MULTI-NEXT: "AddressSize": "64bit", +# MULTI-NEXT: "LoadName": "" +# MULTI-NEXT: } + +## Test outputting file summary for an archive with a single file. +# RUN: yaml2obj %s -o %t.trivial.obj.elf-x86-64 +# RUN: llvm-ar rc %t.a %t.trivial.obj.elf-x86-64 +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.a | FileCheck %s -DARFILE="%t.a" --check-prefix=ARCH-SINGLE + +# ARCH-SINGLE: "FileSummary": { +# ARCH-SINGLE-NEXT: "File": "[[ARFILE]]({{.*}}trivial.obj.elf-x86-64)", +# 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: } + +## Test outputting file summary for an archive with a single files. +# RUN: yaml2obj %s -o %t.trivial.obj.elf-x86-64 +# RUN: llvm-ar rc %t.a %t.trivial.obj.elf-x86-64 %t.trivial.obj.elf-x86-64 +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.a | FileCheck %s -DARFILE="%t.a" --check-prefix=ARCH-MULTI + +# ARCH-MULTI: "FileSummary": { +# ARCH-MULTI-NEXT: "File": "[[ARFILE]]({{.*}}trivial.obj.elf-x86-64)", +# 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: "FileSummary": { +# ARCH-MULTI-NEXT: "File": "[[ARFILE]]({{.*}}trivial.obj.elf-x86-64)", +# 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: } + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 Index: llvm/test/tools/llvm-readobj/ELF/output-style.test =================================================================== --- llvm/test/tools/llvm-readobj/ELF/output-style.test +++ 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' Index: llvm/test/tools/llvm-readobj/ELF/pretty-print.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-readobj/ELF/pretty-print.test @@ -0,0 +1,44 @@ +## Test the JSON pretty-print flag. + +## Test JSON with pretty-print off +# RUN: yaml2obj %s -o %t.trivial.obj.elf-x86-64 +# RUN: llvm-readobj --elf-output-style=JSON %t.trivial.obj.elf-x86-64 | FileCheck -DFILE=%t.trivial.obj.elf-x86-64 %s --check-prefix=NO-PRETTY + +# NO-PRETTY: [ +# NO-PRETTY-SAME: { +# NO-PRETTY-SAME: "[[FILE]]":{ +# NO-PRETTY-SAME: "FileSummary":{ +# NO-PRETTY-SAME: "File":"{{.*}}trivial.obj.elf-x86-64", +# 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: yaml2obj %s -o %t.trivial.obj.elf-x86-64 +# RUN: llvm-readobj --elf-output-style=JSON --pretty-print %t.trivial.obj.elf-x86-64 | FileCheck -DFILE=%t.trivial.obj.elf-x86-64 %s --check-prefix=PRETTY + +# PRETTY: [ +# PRETTY-NEXT: { +# PRETTY-NEXT: "[[FILE]]": { +# PRETTY-NEXT: "FileSummary": { +# PRETTY-NEXT: "File": "{{.*}}trivial.obj.elf-x86-64", +# 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 Index: llvm/tools/llvm-readobj/ELFDumper.cpp =================================================================== --- llvm/tools/llvm-readobj/ELFDumper.cpp +++ 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; @@ -705,9 +709,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 { @@ -717,6 +739,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); } @@ -3231,6 +3255,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"; @@ -7299,3 +7333,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(); +} Index: llvm/tools/llvm-readobj/ObjDumper.h =================================================================== --- llvm/tools/llvm-readobj/ObjDumper.h +++ 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; Index: llvm/tools/llvm-readobj/ObjDumper.cpp =================================================================== --- llvm/tools/llvm-readobj/ObjDumper.cpp +++ 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) { Index: llvm/tools/llvm-readobj/Opts.td =================================================================== --- llvm/tools/llvm-readobj/Opts.td +++ 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">; 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; Index: llvm/tools/llvm-readobj/llvm-readobj.h =================================================================== --- llvm/tools/llvm-readobj/llvm-readobj.h +++ 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 Index: llvm/tools/llvm-readobj/llvm-readobj.cpp =================================================================== --- llvm/tools/llvm-readobj/llvm-readobj.cpp +++ 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); + return std::make_unique(fouts()); +} + int main(int argc, char *argv[]) { InitLLVM X(argc, argv); BumpPtrAllocator A; @@ -610,16 +612,20 @@ opts::SectionHeaders = true; } - ScopedPrinter Writer(fouts()); + std::unique_ptr Writer = createWriter(); + std::unique_ptr D; + if (opts::Output == opts::JSON) + D = std::make_unique(*Writer.get()); + 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()); }