[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