[llvm] 05d1ae4 - * Add support for JSON output style to llvm-symbolizer
Alex Orlov via llvm-commits
llvm-commits at lists.llvm.org
Tue May 11 02:11:05 PDT 2021
Author: Alex Orlov
Date: 2021-05-11T13:10:54+04:00
New Revision: 05d1ae4e18fa565ea522e02d2497ec68d1dbdd80
URL: https://github.com/llvm/llvm-project/commit/05d1ae4e18fa565ea522e02d2497ec68d1dbdd80
DIFF: https://github.com/llvm/llvm-project/commit/05d1ae4e18fa565ea522e02d2497ec68d1dbdd80.diff
LOG: * Add support for JSON output style to llvm-symbolizer
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
Added:
llvm/test/tools/llvm-symbolizer/output-style-json-code.test
llvm/test/tools/llvm-symbolizer/output-style-json-data.test
llvm/test/tools/llvm-symbolizer/output-style-json-frame.ll
Modified:
llvm/docs/CommandGuide/llvm-symbolizer.rst
llvm/include/llvm/DebugInfo/Symbolize/DIPrinter.h
llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp
llvm/tools/llvm-symbolizer/Opts.td
llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
Removed:
################################################################################
diff --git a/llvm/docs/CommandGuide/llvm-symbolizer.rst b/llvm/docs/CommandGuide/llvm-symbolizer.rst
index 8afdbf40cd66..4ede77510394 100644
--- a/llvm/docs/CommandGuide/llvm-symbolizer.rst
+++ b/llvm/docs/CommandGuide/llvm-symbolizer.rst
@@ -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**.
@@ -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
@@ -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
diff --git a/llvm/include/llvm/DebugInfo/Symbolize/DIPrinter.h b/llvm/include/llvm/DebugInfo/Symbolize/DIPrinter.h
index 66409d86c6da..0b223af8c47f 100644
--- a/llvm/include/llvm/DebugInfo/Symbolize/DIPrinter.h
+++ b/llvm/include/llvm/DebugInfo/Symbolize/DIPrinter.h
@@ -15,6 +15,7 @@
#define LLVM_DEBUGINFO_SYMBOLIZE_DIPRINTER_H
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/JSON.h"
#include <string>
#include <vector>
@@ -30,7 +31,7 @@ namespace symbolize {
struct Request {
StringRef ModuleName;
- uint64_t Address = 0;
+ Optional<uint64_t> Address;
};
class DIPrinter {
@@ -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 {
@@ -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 {
@@ -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
diff --git a/llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp b/llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp
index afed1bafee8b..6d2dd346de57 100644
--- a/llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp
+++ b/llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp
@@ -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);
@@ -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;
@@ -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
@@ -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,
@@ -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
diff --git a/llvm/test/tools/llvm-symbolizer/output-style-json-code.test b/llvm/test/tools/llvm-symbolizer/output-style-json-code.test
new file mode 100644
index 000000000000..7e1008debd4e
--- /dev/null
+++ b/llvm/test/tools/llvm-symbolizer/output-style-json-code.test
@@ -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"}
diff --git a/llvm/test/tools/llvm-symbolizer/output-style-json-data.test b/llvm/test/tools/llvm-symbolizer/output-style-json-data.test
new file mode 100644
index 000000000000..f77cf40b9074
--- /dev/null
+++ b/llvm/test/tools/llvm-symbolizer/output-style-json-data.test
@@ -0,0 +1,38 @@
+## This test checks JSON output for DATA.
+
+# REQUIRES: x86-registered-target
+
+## Show how library errors are reported in the output.
+# RUN: llvm-symbolizer "DATA %t-no-file.o 0" --output-style=JSON | \
+# 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.o"}]
+
+## Handle invalid argument.
+# RUN: llvm-symbolizer "DATA tmp.o Z" --output-style=JSON | \
+# RUN: FileCheck %s --check-prefix=INVARG --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+# INVARG:[{"Error":{"Message":"unable to parse arguments: DATA tmp.o Z"},"ModuleName":"tmp.o"}]
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
+
+## Resolve out of range address.
+# RUN: llvm-symbolizer "DATA %t.o 0x10000000" --output-style=JSON | \
+# RUN: FileCheck %s --check-prefix=NOT-FOUND --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+# NOT-FOUND:[{"Address":"0x10000000","Data":{"Name":"","Size":"0x0","Start":"0x0"},"ModuleName":"{{.*}}.o"}]
+
+## Resolve valid address.
+# RUN: llvm-symbolizer "DATA %t.o 0" --output-style=JSON | \
+# RUN: FileCheck %s --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+# CHECK:[{"Address":"0x0","Data":{"Name":"d1","Size":"0x8","Start":"0x0"},"ModuleName":"{{.*}}.o"}]
+
+## Test multiple addresses on the command-line.
+# RUN: llvm-symbolizer -e=%t.o "DATA 2" "DATA 8" --output-style=JSON | \
+# RUN: FileCheck %s --check-prefix=MULTI --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+# MULTI:[{"Address":"0x2","Data":{"Name":"d1","Size":"0x8","Start":"0x0"},"ModuleName":"{{.*}}.o"},{"Address":"0x8","Data":{"Name":"d2","Size":"0x4","Start":"0x8"},"ModuleName":"{{.*}}.o"}]
+
+d1:
+ .quad 0x1122334455667788
+ .size d1, 8
+
+d2:
+ .long 0x99aabbcc
+ .size d2, 4
diff --git a/llvm/test/tools/llvm-symbolizer/output-style-json-frame.ll b/llvm/test/tools/llvm-symbolizer/output-style-json-frame.ll
new file mode 100644
index 000000000000..9cdbf4f1c13d
--- /dev/null
+++ b/llvm/test/tools/llvm-symbolizer/output-style-json-frame.ll
@@ -0,0 +1,68 @@
+;; This test checks JSON output for FRAME.
+
+; REQUIRES: aarch64-registered-target
+
+;; Show how library errors are reported in the output.
+; RUN: llvm-symbolizer "FRAME %t-no-file.o 0" --output-style=JSON | \
+; 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.o"}]
+
+;; Handle invalid argument.
+; RUN: llvm-symbolizer "FRAME tmp.o Z" --output-style=JSON | \
+; RUN: FileCheck %s --check-prefix=INVARG --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+; INVARG:[{"Error":{"Message":"unable to parse arguments: FRAME tmp.o Z"},"ModuleName":"tmp.o"}]
+
+; RUN: llc -filetype=obj -o %t.o %s
+
+;; Resolve out of range address. Expected an empty array.
+; RUN: llvm-symbolizer "FRAME %t.o 0x10000000" --output-style=JSON | \
+; RUN: FileCheck %s --check-prefix=NOT-FOUND --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+; NOT-FOUND:[{"Address":"0x10000000","Frame":[],"ModuleName":"{{.*}}.o"}]
+
+;; Resolve valid address. Note we check 0, non-zero and missing TagOffset cases.
+; RUN: llvm-symbolizer "FRAME %t.o 0" --output-style=JSON | \
+; RUN: FileCheck %s --strict-whitespace --match-full-lines --implicit-check-not={{.}}
+; CHECK:[{"Address":"0x0","Frame":[{"DeclFile":"/x.c","DeclLine":2,"FrameOffset":24,"FunctionName":"f","Name":"a","Size":"0x8","TagOffset":"0x0"},{"DeclFile":"/x.c","DeclLine":3,"FrameOffset":16,"FunctionName":"f","Name":"b","Size":"0x8","TagOffset":"0x1"},{"DeclFile":"/x.c","DeclLine":4,"FrameOffset":12,"FunctionName":"f","Name":"c","Size":"0x4","TagOffset":""}],"ModuleName":"{{.*}}.o"}]
+
+target triple="aarch64--"
+
+define void @f() !dbg !6 {
+entry:
+ %a = alloca i8*
+ %b = alloca i8*
+ %c = alloca i32 ; To check a variable with a
diff erent size.
+ ; Note: The following 2 lines declares the tag offsets we are checking in this test.
+ ; The tag offset for the 3rd variable is missing for purpose.
+ call void @llvm.dbg.declare(metadata i8** %a, metadata !12, metadata !DIExpression(DW_OP_LLVM_tag_offset, 0)), !dbg !15
+ call void @llvm.dbg.declare(metadata i8** %b, metadata !13, metadata !DIExpression(DW_OP_LLVM_tag_offset, 1)), !dbg !16
+ call void @llvm.dbg.declare(metadata i32* %c, metadata !14, metadata !DIExpression()), !dbg !17
+ ret void, !dbg !18
+}
+
+declare void @llvm.dbg.declare(metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+!llvm.ident = !{!5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "x.c", directory: "/")
+!2 = !{}
+!3 = !{i32 2, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{!"clang"}
+!6 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !7, isLocal: false, isDefinition: true, scopeLine: 1, flags:
+DIFlagPrototyped, isOptimized: false, unit: !0, retainedNodes: !2)
+!7 = !DISubroutineType(types: !8)
+!8 = !{null, !9}
+!9 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64)
+!10 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !11)
+!11 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+!19 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!12 = !DILocalVariable(name: "a", scope: !6, file: !1, line: 2, type: !9)
+!13 = !DILocalVariable(name: "b", scope: !6, file: !1, line: 3, type: !9)
+!14 = !DILocalVariable(name: "c", scope: !6, file: !1, line: 4, type: !19)
+!15 = !DILocation(line: 2, column: 10, scope: !6)
+!16 = !DILocation(line: 3, column: 11, scope: !6)
+!17 = !DILocation(line: 4, column: 12, scope: !6)
+!18 = !DILocation(line: 5, column: 13, scope: !6)
diff --git a/llvm/tools/llvm-symbolizer/Opts.td b/llvm/tools/llvm-symbolizer/Opts.td
index ac23639f130e..227969ef6731 100644
--- a/llvm/tools/llvm-symbolizer/Opts.td
+++ b/llvm/tools/llvm-symbolizer/Opts.td
@@ -33,9 +33,9 @@ defm obj
: Eq<"obj", "Path to object file to be symbolized (if not provided, "
"object file should be specified for each input line)">, MetaVarName<"<file>">;
defm output_style
- : Eq<"output-style", "Specify print style. Supported styles: LLVM, GNU">,
+ : Eq<"output-style", "Specify print style. Supported styles: LLVM, GNU, JSON">,
MetaVarName<"style">,
- Values<"LLVM,GNU">;
+ Values<"LLVM,GNU,JSON">;
def pretty_print : F<"pretty-print", "Make the output more human friendly">;
defm print_source_context_lines : Eq<"print-source-context-lines", "Print N lines of source file context">;
def relative_address : F<"relative-address", "Interpret addresses as addresses relative to the image base">;
diff --git a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
index da84a5714150..cc816c024391 100644
--- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
+++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
@@ -91,7 +91,7 @@ static void print(const Request &Request, Expected<T> &ResOrErr,
Printer.print(Request, T());
}
-enum class OutputStyle { LLVM, GNU };
+enum class OutputStyle { LLVM, GNU, JSON };
enum class Command {
Code,
@@ -154,10 +154,7 @@ static void symbolizeInput(const opt::InputArgList &Args, uint64_t AdjustVMA,
uint64_t Offset = 0;
if (!parseCommand(Args.getLastArgValue(OPT_obj_EQ), IsAddr2Line,
StringRef(InputString), Cmd, ModuleName, Offset)) {
- Printer.printInvalidCommand(
- {ModuleName, Offset},
- StringError(InputString,
- std::make_error_code(std::errc::invalid_argument)));
+ Printer.printInvalidCommand({ModuleName, None}, InputString);
return;
}
@@ -320,14 +317,20 @@ int main(int argc, char **argv) {
auto Style = IsAddr2Line ? OutputStyle::GNU : OutputStyle::LLVM;
if (const opt::Arg *A = Args.getLastArg(OPT_output_style_EQ)) {
- Style = strcmp(A->getValue(), "GNU") == 0 ? OutputStyle::GNU
- : OutputStyle::LLVM;
+ if (strcmp(A->getValue(), "GNU") == 0)
+ Style = OutputStyle::GNU;
+ else if (strcmp(A->getValue(), "JSON") == 0)
+ Style = OutputStyle::JSON;
+ else
+ Style = OutputStyle::LLVM;
}
LLVMSymbolizer Symbolizer(Opts);
std::unique_ptr<DIPrinter> Printer;
if (Style == OutputStyle::GNU)
Printer = std::make_unique<GNUPrinter>(outs(), errs(), Config);
+ else if (Style == OutputStyle::JSON)
+ Printer = std::make_unique<JSONPrinter>(outs(), Config);
else
Printer = std::make_unique<LLVMPrinter>(outs(), errs(), Config);
@@ -346,9 +349,11 @@ int main(int argc, char **argv) {
outs().flush();
}
} else {
+ Printer->listBegin();
for (StringRef Address : InputAddresses)
symbolizeInput(Args, AdjustVMA, IsAddr2Line, Style, Address, Symbolizer,
*Printer);
+ Printer->listEnd();
}
return 0;
More information about the llvm-commits
mailing list