Skip to content

Commit

Permalink
* Add support for JSON output style to llvm-symbolizer
Browse files Browse the repository at this point in the history
This patch adds JSON output style to llvm-symbolizer to better support CLI automation by providing a machine readable output.

Reviewed By: jhenderson

Differential Revision: https://reviews.llvm.org/D96883
  • Loading branch information
orlov-alex committed May 11, 2021
1 parent 3339940 commit 05d1ae4
Show file tree
Hide file tree
Showing 8 changed files with 394 additions and 20 deletions.
54 changes: 53 additions & 1 deletion llvm/docs/CommandGuide/llvm-symbolizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ OPTIONS

.. _llvm-symbolizer-opt-output-style:

.. option:: --output-style <LLVM|GNU>
.. option:: --output-style <LLVM|GNU|JSON>

Specify the preferred output style. Defaults to ``LLVM``. When the output
style is set to ``GNU``, the tool follows the style of GNU's **addr2line**.
Expand All @@ -252,6 +252,10 @@ OPTIONS
* Prints an address's debug-data discriminator when it is non-zero. One way to
produce discriminators is to compile with clang's -fdebug-info-for-profiling.

``JSON`` style provides a machine readable output in JSON. If addresses are
supplied via stdin, the output JSON will be a series of individual objects.
Otherwise, all results will be contained in a single array.

.. code-block:: console
$ llvm-symbolizer --obj=inlined.elf 0x4004be 0x400486 -p
Expand All @@ -273,10 +277,58 @@ OPTIONS
$ llvm-symbolizer --output-style=GNU --obj=profiling.elf 0x401167 -p --no-inlines
main at /tmp/test.cpp:15 (discriminator 2)
$ llvm-symbolizer --output-style=JSON --obj=inlined.elf 0x4004be 0x400486 -p
[
{
"Address": "0x4004be",
"ModuleName": "inlined.elf",
"Symbol": [
{
"Column": 18,
"Discriminator": 0,
"FileName": "/tmp/test.cpp",
"FunctionName": "baz()",
"Line": 11,
"Source": "",
"StartFileName": "/tmp/test.cpp",
"StartLine": 9
},
{
"Column": 0,
"Discriminator": 0,
"FileName": "/tmp/test.cpp",
"FunctionName": "main",
"Line": 15,
"Source": "",
"StartFileName": "/tmp/test.cpp",
"StartLine": 14
}
]
},
{
"Address": "0x400486",
"ModuleName": "inlined.elf",
"Symbol": [
{
"Column": 3,
"Discriminator": 0,
"FileName": "/tmp/test.cpp",
"FunctionName": "foo()",
"Line": 6,
"Source": "",
"StartFileName": "/tmp/test.cpp",
"StartLine": 5
}
]
}
]
.. option:: --pretty-print, -p

Print human readable output. If :option:`--inlining` is specified, the
enclosing scope is prefixed by (inlined by).
For JSON output, the option will cause JSON to be indented and split over
new lines. Otherwise, the JSON output will be printed in a compact form.

.. code-block:: console
Expand Down
45 changes: 41 additions & 4 deletions llvm/include/llvm/DebugInfo/Symbolize/DIPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define LLVM_DEBUGINFO_SYMBOLIZE_DIPRINTER_H

#include "llvm/ADT/StringRef.h"
#include "llvm/Support/JSON.h"
#include <string>
#include <vector>

Expand All @@ -30,7 +31,7 @@ namespace symbolize {

struct Request {
StringRef ModuleName;
uint64_t Address = 0;
Optional<uint64_t> Address;
};

class DIPrinter {
Expand All @@ -45,11 +46,14 @@ class DIPrinter {
const std::vector<DILocal> &Locals) = 0;

virtual void printInvalidCommand(const Request &Request,
const ErrorInfoBase &ErrorInfo) = 0;
StringRef Command) = 0;

virtual bool printError(const Request &Request,
const ErrorInfoBase &ErrorInfo,
StringRef ErrorBanner) = 0;

virtual void listBegin() = 0;
virtual void listEnd() = 0;
};

struct PrinterConfig {
Expand Down Expand Up @@ -87,11 +91,13 @@ class PlainPrinterBase : public DIPrinter {
void print(const Request &Request,
const std::vector<DILocal> &Locals) override;

void printInvalidCommand(const Request &Request,
const ErrorInfoBase &ErrorInfo) override;
void printInvalidCommand(const Request &Request, StringRef Command) override;

bool printError(const Request &Request, const ErrorInfoBase &ErrorInfo,
StringRef ErrorBanner) override;

void listBegin() override{};
void listEnd() override{};
};

class LLVMPrinter : public PlainPrinterBase {
Expand All @@ -112,6 +118,37 @@ class GNUPrinter : public PlainPrinterBase {
GNUPrinter(raw_ostream &OS, raw_ostream &ES, PrinterConfig &Config)
: PlainPrinterBase(OS, ES, Config) {}
};

class JSONPrinter : public DIPrinter {
private:
raw_ostream &OS;
PrinterConfig Config;
std::unique_ptr<json::Array> ObjectList;

void printJSON(const json::Value &V) {
json::OStream JOS(OS, Config.Pretty ? 2 : 0);
JOS.value(V);
OS << '\n';
}

public:
JSONPrinter(raw_ostream &OS, PrinterConfig &Config)
: DIPrinter(), OS(OS), Config(Config) {}

void print(const Request &Request, const DILineInfo &Info) override;
void print(const Request &Request, const DIInliningInfo &Info) override;
void print(const Request &Request, const DIGlobal &Global) override;
void print(const Request &Request,
const std::vector<DILocal> &Locals) override;

void printInvalidCommand(const Request &Request, StringRef Command) override;

bool printError(const Request &Request, const ErrorInfoBase &ErrorInfo,
StringRef ErrorBanner) override;

void listBegin() override;
void listEnd() override;
};
} // namespace symbolize
} // namespace llvm

Expand Down
123 changes: 117 additions & 6 deletions llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ void PlainPrinterBase::print(const DILineInfo &Info, bool Inlined) {
}

void PlainPrinterBase::print(const Request &Request, const DILineInfo &Info) {
printHeader(Request.Address);
printHeader(*Request.Address);
print(Info, false);
printFooter();
}

void PlainPrinterBase::print(const Request &Request,
const DIInliningInfo &Info) {
printHeader(Request.Address);
printHeader(*Request.Address);
uint32_t FramesNum = Info.getNumberOfFrames();
if (FramesNum == 0)
print(DILineInfo(), false);
Expand All @@ -139,7 +139,7 @@ void PlainPrinterBase::print(const Request &Request,
}

void PlainPrinterBase::print(const Request &Request, const DIGlobal &Global) {
printHeader(Request.Address);
printHeader(*Request.Address);
StringRef Name = Global.Name;
if (Name == DILineInfo::BadString)
Name = DILineInfo::Addr2LineBadString;
Expand All @@ -150,7 +150,7 @@ void PlainPrinterBase::print(const Request &Request, const DIGlobal &Global) {

void PlainPrinterBase::print(const Request &Request,
const std::vector<DILocal> &Locals) {
printHeader(Request.Address);
printHeader(*Request.Address);
if (Locals.empty())
OS << DILineInfo::Addr2LineBadString << '\n';
else
Expand Down Expand Up @@ -196,8 +196,8 @@ void PlainPrinterBase::print(const Request &Request,
}

void PlainPrinterBase::printInvalidCommand(const Request &Request,
const ErrorInfoBase &ErrorInfo) {
OS << ErrorInfo.message() << '\n';
StringRef Command) {
OS << Command << '\n';
}

bool PlainPrinterBase::printError(const Request &Request,
Expand All @@ -210,5 +210,116 @@ bool PlainPrinterBase::printError(const Request &Request,
return true;
}

static std::string toHex(uint64_t V) {
return ("0x" + Twine::utohexstr(V)).str();
}

static json::Object toJSON(const Request &Request, StringRef ErrorMsg = "") {
json::Object Json({{"ModuleName", Request.ModuleName.str()}});
if (Request.Address)
Json["Address"] = toHex(*Request.Address);
if (!ErrorMsg.empty())
Json["Error"] = json::Object({{"Message", ErrorMsg.str()}});
return Json;
}

void JSONPrinter::print(const Request &Request, const DILineInfo &Info) {
DIInliningInfo InliningInfo;
InliningInfo.addFrame(Info);
print(Request, InliningInfo);
}

void JSONPrinter::print(const Request &Request, const DIInliningInfo &Info) {
json::Array Array;
for (uint32_t I = 0, N = Info.getNumberOfFrames(); I < N; ++I) {
const DILineInfo &LineInfo = Info.getFrame(I);
Array.push_back(json::Object(
{{"FunctionName", LineInfo.FunctionName != DILineInfo::BadString
? LineInfo.FunctionName
: ""},
{"StartFileName", LineInfo.StartFileName != DILineInfo::BadString
? LineInfo.StartFileName
: ""},
{"StartLine", LineInfo.StartLine},
{"FileName",
LineInfo.FileName != DILineInfo::BadString ? LineInfo.FileName : ""},
{"Line", LineInfo.Line},
{"Column", LineInfo.Column},
{"Discriminator", LineInfo.Discriminator}}));
}
json::Object Json = toJSON(Request);
Json["Symbol"] = std::move(Array);
if (ObjectList)
ObjectList->push_back(std::move(Json));
else
printJSON(std::move(Json));
}

void JSONPrinter::print(const Request &Request, const DIGlobal &Global) {
json::Object Data(
{{"Name", Global.Name != DILineInfo::BadString ? Global.Name : ""},
{"Start", toHex(Global.Start)},
{"Size", toHex(Global.Size)}});
json::Object Json = toJSON(Request);
Json["Data"] = std::move(Data);
if (ObjectList)
ObjectList->push_back(std::move(Json));
else
printJSON(std::move(Json));
}

void JSONPrinter::print(const Request &Request,
const std::vector<DILocal> &Locals) {
json::Array Frame;
for (const DILocal &Local : Locals) {
json::Object FrameObject(
{{"FunctionName", Local.FunctionName},
{"Name", Local.Name},
{"DeclFile", Local.DeclFile},
{"DeclLine", int64_t(Local.DeclLine)},
{"Size", Local.Size ? toHex(*Local.Size) : ""},
{"TagOffset", Local.TagOffset ? toHex(*Local.TagOffset) : ""}});
if (Local.FrameOffset)
FrameObject["FrameOffset"] = *Local.FrameOffset;
Frame.push_back(std::move(FrameObject));
}
json::Object Json = toJSON(Request);
Json["Frame"] = std::move(Frame);
if (ObjectList)
ObjectList->push_back(std::move(Json));
else
printJSON(std::move(Json));
}

void JSONPrinter::printInvalidCommand(const Request &Request,
StringRef Command) {
printError(Request,
StringError("unable to parse arguments: " + Command,
std::make_error_code(std::errc::invalid_argument)),
"");
}

bool JSONPrinter::printError(const Request &Request,
const ErrorInfoBase &ErrorInfo,
StringRef ErrorBanner) {
json::Object Json = toJSON(Request, ErrorInfo.message());
if (ObjectList)
ObjectList->push_back(std::move(Json));
else
printJSON(std::move(Json));
return false;
}

void JSONPrinter::listBegin() {
assert(!ObjectList);
ObjectList = std::make_unique<json::Array>();
}

void JSONPrinter::listEnd() {
assert(ObjectList);
printJSON(std::move(*ObjectList));
ObjectList.release();
}

} // end namespace symbolize
} // end namespace llvm
63 changes: 63 additions & 0 deletions llvm/test/tools/llvm-symbolizer/output-style-json-code.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## This test checks JSON output for CODE.

## If the addresses are specified on the command-line, the output JSON will
## contain an array of the results for all of the given addresses.

## Show how library errors are reported in the output.
# RUN: llvm-symbolizer --output-style=JSON -e %p/no-file.exe 0 | \
# RUN: FileCheck %s -DMSG=%errc_ENOENT --check-prefix=NO-FILE --strict-whitespace --match-full-lines --implicit-check-not={{.}}
# NO-FILE:[{"Address":"0x0","Error":{"Message":"[[MSG]]"},"ModuleName":"{{.*}}/no-file.exe"}]

## Resolve out of range address.
## Expected a list with one empty object with default values.
# RUN: llvm-symbolizer --output-style=JSON -e %p/Inputs/addr.exe 0x10000000 | \
# RUN: FileCheck %s --check-prefix=NOT-FOUND --strict-whitespace --match-full-lines --implicit-check-not={{.}}
# NOT-FOUND:[{"Address":"0x10000000","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":0,"Discriminator":0,"FileName":"","FunctionName":"","Line":0,"StartFileName":"","StartLine":0}]}]

## Check a non-zero discriminator.
# RUN: llvm-symbolizer --output-style=JSON --obj=%p/Inputs/discrim 0x400575 | \
# RUN: FileCheck %s --check-prefix=DISCRIM --strict-whitespace --match-full-lines --implicit-check-not={{.}}
# DISCRIM:[{"Address":"0x400575","ModuleName":"{{.*}}/Inputs/discrim","Symbol":[{"Column":17,"Discriminator":2,"FileName":"/tmp{{/|\\\\}}discrim.c","FunctionName":"foo","Line":5,"StartFileName":"/tmp{{/|\\\\}}discrim.c","StartLine":4}]}]

## In case of stdin input the output will contain a single JSON object for each input string.

## This test case is testing stdin input, with the --no-inlines option.
# RUN: llvm-symbolizer --output-style=JSON --no-inlines -e %p/Inputs/addr.exe < %p/Inputs/addr.inp | \
# RUN: FileCheck %s --check-prefix=NO-INLINES --strict-whitespace --match-full-lines --implicit-check-not={{.}}
## Invalid first argument before any valid one.
# NO-INLINES:{"Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"}
## Resolve valid address.
# NO-INLINES-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"main","Line":3,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2}]}
## Invalid argument after a valid one.
# NO-INLINES-NEXT:{"Error":{"Message":"unable to parse arguments: some text2"},"ModuleName":"{{.*}}/Inputs/addr.exe"}

## This test case is testing stdin input, inlines by default.
# RUN: llvm-symbolizer --output-style=JSON -e %p/Inputs/addr.exe < %p/Inputs/addr.inp | \
# RUN: FileCheck %s --check-prefix=INLINE --strict-whitespace --match-full-lines --implicit-check-not={{.}}
## Invalid first argument before any valid one.
# INLINE:{"Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"}
## Resolve valid address.
# INLINE-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"inctwo","Line":3,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"inc","Line":7,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"main","Line":14,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12}]}
## Invalid argument after a valid one.
# INLINE-NEXT:{"Error":{"Message":"unable to parse arguments: some text2"},"ModuleName":"{{.*}}/Inputs/addr.exe"}

## Also check the last test case with llvm-adr2line.
## The expected result is the same with -f -i.
# RUN: llvm-addr2line --output-style=JSON -f -i -e %p/Inputs/addr.exe < %p/Inputs/addr.inp | \
# RUN: FileCheck %s --check-prefix=INLINE-A2L --strict-whitespace --match-full-lines --implicit-check-not={{.}}
## Invalid first argument before any valid one.
# INLINE-A2L:{"Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"}
## Resolve valid address.
# INLINE-A2L-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"inctwo","Line":3,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"inc","Line":7,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"main","Line":14,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12}]}
## Invalid argument after a valid one.
# INLINE-A2L-NEXT:{"Error":{"Message":"unable to parse arguments: some text2"},"ModuleName":"{{.*}}/Inputs/addr.exe"}

## Note llvm-addr2line without -f does not print the function name in JSON too.
# RUN: llvm-addr2line --output-style=JSON -i -e %p/Inputs/addr.exe < %p/Inputs/addr.inp | \
# RUN: FileCheck %s --check-prefix=NO-FUNC-A2L --strict-whitespace --match-full-lines --implicit-check-not={{.}}
## Invalid first argument before any valid one.
# NO-FUNC-A2L:{"Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"}
## Resolve valid address.
# NO-FUNC-A2L-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"","Line":3,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"","Line":7,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"","Line":14,"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12}]}
## Invalid argument after a valid one.
# NO-FUNC-A2L-NEXT:{"Error":{"Message":"unable to parse arguments: some text2"},"ModuleName":"{{.*}}/Inputs/addr.exe"}
Loading

0 comments on commit 05d1ae4

Please sign in to comment.