Skip to content

Commit

Permalink
[llvm-readobj] Add JSONScopedPrinter to llvm-readelf
Browse files Browse the repository at this point in the history
Adds JSONScopedPrinter to llvm-readelf. It includes an empty
JSONELFDumper class which will be used to override any LLVMELFDumper
methods which utilize startLine() which JSONScopedPrinter cannot
provide.

This introduces a change where calls to llvm-readelf with non-ELF object
files that specify --elf-output-style=GNU will now print file summary
information where it previously didn't.

Fixes previous Windows test failure which occured due to JSON escaping
of '\' by not relying on LIT substitution.

Reviewed By: jhenderson

Differential Revision: https://reviews.llvm.org/D114225
  • Loading branch information
Jaysonyan committed Dec 10, 2021
1 parent f2e945a commit 1f35d7b
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 32 deletions.
12 changes: 9 additions & 3 deletions llvm/docs/CommandGuide/llvm-readelf.rst
Expand Up @@ -72,9 +72,10 @@ OPTIONS

.. option:: --elf-output-style=<value>

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

Expand Down Expand Up @@ -127,6 +128,11 @@ OPTIONS

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.
Expand Down
12 changes: 9 additions & 3 deletions llvm/docs/CommandGuide/llvm-readobj.rst
Expand Up @@ -183,9 +183,10 @@ The following options are implemented only for the ELF file format.

.. option:: --elf-output-style=<value>

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

Expand All @@ -207,6 +208,11 @@ The following options are implemented only for the ELF file format.

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.
Expand Down
114 changes: 114 additions & 0 deletions 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": "<Not found>"
# 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": "<Not found>"
# 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": "<Not found>"
# 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": "<Not found>"
# 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": "<Not found>"
# 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": "<Not found>"
# 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
2 changes: 1 addition & 1 deletion 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'
48 changes: 48 additions & 0 deletions 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":"<Not found>"
# 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": "<Not found>"
# PRETTY-NEXT: }
# PRETTY-NEXT: }
# PRETTY-NEXT: }
# PRETTY-NEXT:]

--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_REL
Machine: EM_X86_64
49 changes: 49 additions & 0 deletions llvm/tools/llvm-readobj/ELFDumper.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -548,6 +549,9 @@ template <typename ELFT> class GNUELFDumper : public ELFDumper<ELFT> {
assert(&this->W.getOStream() == &llvm::fouts());
}

void printFileSummary(StringRef FileStr, ObjectFile &Obj,
ArrayRef<std::string> InputFilenames,
const Archive *A) override;
void printFileHeaders() override;
void printGroupSections() override;
void printRelocations() override;
Expand Down Expand Up @@ -697,9 +701,27 @@ template <typename ELFT> class LLVMELFDumper : public ELFDumper<ELFT> {
void printMipsPLT(const MipsGOTParser<ELFT> &Parser) override;
void printMipsABIFlags() override;

protected:
ScopedPrinter &W;
};

// JSONELFDumper shares most of the same implementation as LLVMELFDumper except
// it uses a JSONScopedPrinter.
template <typename ELFT> class JSONELFDumper : public LLVMELFDumper<ELFT> {
public:
LLVM_ELF_IMPORT_TYPES_ELFT(ELFT)

JSONELFDumper(const object::ELFObjectFile<ELFT> &ObjF, ScopedPrinter &Writer)
: LLVMELFDumper<ELFT>(ObjF, Writer) {}

void printFileSummary(StringRef FileStr, ObjectFile &Obj,
ArrayRef<std::string> InputFilenames,
const Archive *A) override;

private:
std::unique_ptr<DictScope> FileScope;
};

} // end anonymous namespace

namespace llvm {
Expand All @@ -709,6 +731,8 @@ static std::unique_ptr<ObjDumper>
createELFDumper(const ELFObjectFile<ELFT> &Obj, ScopedPrinter &Writer) {
if (opts::Output == opts::GNU)
return std::make_unique<GNUELFDumper<ELFT>>(Obj, Writer);
else if (opts::Output == opts::JSON)
return std::make_unique<JSONELFDumper<ELFT>>(Obj, Writer);
return std::make_unique<LLVMELFDumper<ELFT>>(Obj, Writer);
}

Expand Down Expand Up @@ -3225,6 +3249,16 @@ static const EnumEntry<unsigned> *getObjectFileEnumEntry(unsigned Type) {
return nullptr;
}

template <class ELFT>
void GNUELFDumper<ELFT>::printFileSummary(StringRef FileStr, ObjectFile &Obj,
ArrayRef<std::string> InputFilenames,
const Archive *A) {
if (InputFilenames.size() > 1 || A) {
this->W.startLine() << "\n";
this->W.printString("File", FileStr);
}
}

template <class ELFT> void GNUELFDumper<ELFT>::printFileHeaders() {
const Elf_Ehdr &e = this->Obj.getHeader();
OS << "ELF Header:\n";
Expand Down Expand Up @@ -7306,3 +7340,18 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printMipsABIFlags() {
W.printFlags("Flags 1", Flags->flags1, makeArrayRef(ElfMipsFlags1));
W.printHex("Flags 2", Flags->flags2);
}

template <class ELFT>
void JSONELFDumper<ELFT>::printFileSummary(StringRef FileStr, ObjectFile &Obj,
ArrayRef<std::string> InputFilenames,
const Archive *A) {
FileScope = std::make_unique<DictScope>(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();
}
13 changes: 13 additions & 0 deletions llvm/tools/llvm-readobj/ObjDumper.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -85,6 +86,18 @@ void ObjDumper::printAsStringList(StringRef StringContent,
}
}

void ObjDumper::printFileSummary(StringRef FileStr, object::ObjectFile &Obj,
ArrayRef<std::string> 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<object::SectionRef>
getSectionRefsByNameOrIndex(const object::ObjectFile &Obj,
ArrayRef<std::string> Sections) {
Expand Down
4 changes: 4 additions & 0 deletions llvm/tools/llvm-readobj/ObjDumper.h
Expand Up @@ -20,6 +20,7 @@

namespace llvm {
namespace object {
class Archive;
class COFFImportFile;
class ObjectFile;
class XCOFFObjectFile;
Expand All @@ -39,6 +40,9 @@ class ObjDumper {

virtual bool canDumpContent() { return true; }

virtual void printFileSummary(StringRef FileStr, object::ObjectFile &Obj,
ArrayRef<std::string> InputFilenames,
const object::Archive *A);
virtual void printFileHeaders() = 0;
virtual void printSectionHeaders() = 0;
virtual void printRelocations() = 0;
Expand Down
3 changes: 2 additions & 1 deletion llvm/tools/llvm-readobj/Opts.td
Expand Up @@ -28,6 +28,7 @@ def expand_relocs : FF<"expand-relocs", "Expand each shown relocation to multipl
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<"<name or index>">;
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">;
Expand All @@ -47,7 +48,7 @@ def unwind : FF<"unwind", "Display unwind information">;
def grp_elf : OptionGroup<"kind">, HelpText<"OPTIONS (ELF specific)">;
def dynamic_table : FF<"dynamic-table", "Display the dynamic section table">, Group<grp_elf>;
def elf_linker_options : FF<"elf-linker-options", "Display the .linker-options section">, Group<grp_elf>;
defm elf_output_style : Eq<"elf-output-style", "Specify ELF dump style: LLVM or GNU">, Group<grp_elf>;
defm elf_output_style : Eq<"elf-output-style", "Specify ELF dump style: LLVM, GNU, JSON">, Group<grp_elf>;
def histogram : FF<"histogram", "Display bucket list histogram for hash sections">, Group<grp_elf>;
def section_groups : FF<"section-groups", "Display section groups">, Group<grp_elf>;
def gnu_hash_table : FF<"gnu-hash-table", "Display the GNU hash table for dynamic symbols">, Group<grp_elf>;
Expand Down

0 comments on commit 1f35d7b

Please sign in to comment.