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,13 +31,13 @@ struct Request { StringRef ModuleName; - uint64_t Address = 0; + Optional Address; }; class DIPrinter { public: - DIPrinter(){}; - virtual ~DIPrinter(){}; + DIPrinter() {} + virtual ~DIPrinter() {} virtual void print(const Request &Request, const DILineInfo &Info) = 0; virtual void print(const Request &Request, const DIInliningInfo &Info) = 0; @@ -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,116 @@ 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 &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(); +} + +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 --- /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 --- /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 --- /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 different 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 --- 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;