diff --git a/llvm/docs/CommandGuide/llvm-symbolizer.rst b/llvm/docs/CommandGuide/llvm-symbolizer.rst --- a/llvm/docs/CommandGuide/llvm-symbolizer.rst +++ b/llvm/docs/CommandGuide/llvm-symbolizer.rst @@ -236,7 +236,7 @@ .. _llvm-symbolizer-opt-output-style: -.. option:: --output-style +.. option:: --output-style 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**. @@ -253,6 +253,10 @@ * 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 @@ -274,10 +278,58 @@ $ 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 --- 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 #include @@ -30,7 +31,7 @@ struct Request { StringRef ModuleName; - uint64_t Address = 0; + Optional Address; }; class DIPrinter { @@ -45,11 +46,14 @@ const std::vector &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 @@ void print(const Request &Request, const std::vector &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 @@ 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 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 &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 --- a/llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp +++ b/llvm/lib/DebugInfo/Symbolize/DIPrinter.cpp @@ -121,14 +121,14 @@ } 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, 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 std::vector &Locals) { - printHeader(Request.Address); + printHeader(*Request.Address); if (Locals.empty()) OS << DILineInfo::Addr2LineBadString << '\n'; else @@ -196,8 +196,8 @@ } 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,134 @@ return true; } +static std::string toHex(uint64_t V) { + return ("0x" + Twine::utohexstr(V)).str(); +} + +static json::Object toJSON(const Request &Request, int ErrorCode = 0, + const StringRef ErrorMsg = "") { + json::Object J({{"ModuleName", std::string(Request.ModuleName)}}); + if (Request.Address) + J["Address"] = toHex(*Request.Address); + if (!ErrorMsg.empty()) + J["Error"] = json::Object({{"Message", std::string(ErrorMsg)}}); + return J; +} + +void JSONPrinter::print(const Request &Request, const DILineInfo &Info) { + DIInliningInfo I; + I.addFrame(Info); + print(Request, I); +} + +void JSONPrinter::print(const Request &Request, const DIInliningInfo &Info) { + json::Array A; + uint32_t N = Info.getNumberOfFrames(); + for (uint32_t I = 0; I < N; ++I) { + const DILineInfo &LI = Info.getFrame(I); + json::Object O( + {{"FunctionName", + LI.FunctionName != DILineInfo::BadString ? LI.FunctionName : ""}, + {"StartFileName", + LI.StartFileName != DILineInfo::BadString ? LI.StartFileName : ""}, + {"StartLine", LI.StartLine}, + {"FileName", LI.FileName != DILineInfo::BadString ? LI.FileName : ""}, + {"Line", LI.Line}, + {"Column", LI.Column}, + {"Discriminator", LI.Discriminator}}); + if (LI.Source && Config.SourceContextLines > 0) { + int64_t FirstLine = + std::max(static_cast(1), + int64_t(LI.Line) - Config.SourceContextLines / 2); + int64_t LastLine = FirstLine + Config.SourceContextLines; + size_t P1 = StringRef::npos, P = 0; + for (int64_t L = 1; L < LastLine; ++L, ++P) { + if (L == FirstLine) + P1 = P; + P = LI.Source->find('\n', P); + if (P == StringRef::npos) + break; + } + if (P1 != StringRef::npos) + O["Source"] = + std::string((P == StringRef::npos) ? LI.Source->substr(P1) + : LI.Source->substr(P1, P - P1)); + } + A.push_back(std::move(O)); + } + json::Object J = toJSON(Request); + J["Symbol"] = std::move(A); + if (ObjectList) + ObjectList->push_back(std::move(J)); + else + printJSON(std::move(J)); +} + +void JSONPrinter::print(const Request &Request, const DIGlobal &Global) { + json::Object D( + {{"Name", Global.Name != DILineInfo::BadString ? Global.Name : ""}, + {"Start", toHex(Global.Start)}, + {"Size", toHex(Global.Size)}}); + json::Object J = toJSON(Request); + J["Data"] = std::move(D); + if (ObjectList) + ObjectList->push_back(std::move(J)); + else + printJSON(std::move(J)); +} + +void JSONPrinter::print(const Request &Request, + const std::vector &Locals) { + json::Array A; + for (const DILocal &L : Locals) { + json::Object D({{"FunctionName", L.FunctionName}, + {"Name", L.Name}, + {"DeclFile", L.DeclFile}, + {"DeclLine", int64_t(L.DeclLine)}, + {"Size", L.Size ? toHex(*L.Size) : ""}, + {"TagOffset", L.TagOffset ? toHex(*L.TagOffset) : ""}}); + if (L.FrameOffset) + D["FrameOffset"] = *L.FrameOffset; + A.push_back(std::move(D)); + } + json::Object J = toJSON(Request); + J["Frame"] = std::move(A); + if (ObjectList) + ObjectList->push_back(std::move(J)); + else + printJSON(std::move(J)); +} + +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 J = toJSON(Request, ErrorInfo.convertToErrorCode().value(), + ErrorInfo.message()); + if (ObjectList) + ObjectList->push_back(std::move(J)); + else + printJSON(std::move(J)); + return false; +} + +void JSONPrinter::listBegin() { + assert(!ObjectList); + ObjectList = std::make_unique(); +} + +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-source.c b/llvm/test/tools/llvm-symbolizer/output-style-json-code-source.c new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-symbolizer/output-style-json-code-source.c @@ -0,0 +1,6 @@ +void foo() {} + +// RUN: clang --target=x86_64-pc-linux -gdwarf-5 -gembed-source -g -c %s -o %t.o +// RUN: llvm-symbolizer --output-style=JSON --print-source-context-lines=2 --obj=%t.o 0 | \ +// RUN: FileCheck %s --strict-whitespace --match-full-lines --implicit-check-not={{.}} +// CHECK:[{"Address":"0x0","ModuleName":"{{.*}}.o","Symbol":[{"Column":0,"Discriminator":0,"FileName":"{{.*}}output-style-json-code-source.c","FunctionName":"foo","Line":1,"Source":"void foo() {}\n\n","StartFileName":"{{.*}}output-style-json-code-source.c","StartLine":1}]}] 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 --- /dev/null +++ b/llvm/test/tools/llvm-symbolizer/output-style-json-code.test @@ -0,0 +1,53 @@ +## This test checks JSON output for CODE. + +## If the addresses are specified in the command line, the output JSON will +## contain an array of the results for all of the given addresses. + +## Handle symbolizer library error - file does not exist. +# 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 1000000000 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND --strict-whitespace --match-full-lines --implicit-check-not={{.}} +# NOT-FOUND:[{"Address":"0x3b9aca00","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"} 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 --- /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 + +## Handle symbolize library error - file does not exist. +# 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 1000000000" --output-style=JSON | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND --strict-whitespace --match-full-lines --implicit-check-not={{.}} +# NOT-FOUND:[{"Address":"0x3b9aca00","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 in 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.c b/llvm/test/tools/llvm-symbolizer/output-style-json-frame.c new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-symbolizer/output-style-json-frame.c @@ -0,0 +1,35 @@ +void g(void *x, void *y, void *z); + +void f() { + char *a; + char b[16]; + char c[32]; + g(a,b,c); +} + +// This test checks JSON output for FRAME. + +// REQUIRES: aarch64-registered-target + +// Handle symbolize library error - file does not exist. +// 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: clang --target=aarch64-linux-android -fsanitize=hwaddress -fsanitize-hwaddress-abi=platform %s -O -g -S -o %t.s +// RUN: llvm-mc -filetype=obj -triple=aarch64-linux-android -o %t.o %t.s + +// Resolve out of range address. Expected an empty array. +// RUN: llvm-symbolizer "FRAME %t.o 1000000000" --output-style=JSON | \ +// RUN: FileCheck %s --check-prefix=NOT-FOUND --strict-whitespace --match-full-lines --implicit-check-not={{.}} +// NOT-FOUND:[{"Address":"0x3b9aca00","Frame":[],"ModuleName":"{{.*}}.o"}] + +// Resolve valid address. +// 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":"{{.*}}output-style-json-frame.c","DeclLine":5,"FrameOffset":-16,"FunctionName":"f","Name":"b","Size":"0x10","TagOffset":"0x0"},{"DeclFile":"{{.*}}output-style-json-frame.c","DeclLine":6,"FrameOffset":-48,"FunctionName":"f","Name":"c","Size":"0x20","TagOffset":"0x80"},{"DeclFile":"{{.*}}output-style-json-frame.c","DeclLine":4,"FunctionName":"f","Name":"a","Size":"0x8","TagOffset":""}],"ModuleName":"{{.*}}.o"}] diff --git a/llvm/tools/llvm-symbolizer/Opts.td b/llvm/tools/llvm-symbolizer/Opts.td --- a/llvm/tools/llvm-symbolizer/Opts.td +++ b/llvm/tools/llvm-symbolizer/Opts.td @@ -33,9 +33,9 @@ : Eq<"obj", "Path to object file to be symbolized (if not provided, " "object file should be specified for each input line)">, MetaVarName<"">; 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 --- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -91,7 +91,7 @@ Printer.print(Request, T()); } -enum class OutputStyle { LLVM, GNU }; +enum class OutputStyle { LLVM, GNU, JSON }; enum class Command { Code, @@ -154,10 +154,7 @@ 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 @@ 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 Printer; if (Style == OutputStyle::GNU) Printer = std::make_unique(outs(), errs(), Config); + else if (Style == OutputStyle::JSON) + Printer = std::make_unique(outs(), Config); else Printer = std::make_unique(outs(), errs(), Config); @@ -346,9 +349,11 @@ outs().flush(); } } else { + Printer->listBegin(); for (StringRef Address : InputAddresses) symbolizeInput(Args, AdjustVMA, IsAddr2Line, Style, Address, Symbolizer, *Printer); + Printer->listEnd(); } return 0;