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,8 @@ * 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. + .. code-block:: console $ llvm-symbolizer --obj=inlined.elf 0x4004be 0x400486 -p @@ -274,6 +276,16 @@ $ 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 + [{"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 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 @@ -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; + bool Pretty; + std::unique_ptr List; + + void printJSON(const json::Value &V) { + json::OStream JOS(OS, Pretty ? 2 : 0); + JOS.value(V); + OS << '\n'; + } + +public: + JSONPrinter(raw_ostream &OS, bool Pretty = false) + : DIPrinter(), OS(OS), Pretty(Pretty) {} + + 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 @@ -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,115 @@ 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)}, + {"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); + A.push_back(json::Object( + {{"Source", LI.Source ? std::string(*LI.Source) : ""}, + {"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}})); + } + json::Object J = toJSON(Request); + J["Symbol"] = std::move(A); + if (List) + List->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 (List) + List->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 (List) + List->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 (List) + List->push_back(std::move(J)); + else + printJSON(std::move(J)); + return false; +} + +void JSONPrinter::listBegin() { + assert(!List); + List = std::make_unique(); +} + +void JSONPrinter::listEnd() { + assert(List); + printJSON(std::move(*List)); + List.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,60 @@ +## This test checks JSON output for CODE. + +## Handle symbolize library error - file does not exist, no-inlines. +# RUN: llvm-symbolizer --output-style=JSON --no-inlines -e %p/no-file.exe 0 | \ +# RUN: FileCheck %s -DMSG=%errc_ENOENT --check-prefix=NO-FILE --strict-whitespace --match-full-lines --implicit-check-not={{.}} + +## Handle symbolize library error - file does not exist, inline. +# 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, no-inlines. Expected empty object with default values. +# RUN: llvm-symbolizer --output-style=JSON --no-inlines -e %p/Inputs/addr.exe 1000000000 | \ +# RUN: FileCheck %s --check-prefix=NOT-FOUND-1 --strict-whitespace --match-full-lines --implicit-check-not={{.}} + +# NOT-FOUND-1:[{"Address":"0x3b9aca00","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":0,"Discriminator":0,"FileName":"","FunctionName":"","Line":0,"Source":"","StartFileName":"","StartLine":0}]}] + +## Resolve out of range address, inlines. Expected a Frames 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-2 --strict-whitespace --match-full-lines --implicit-check-not={{.}} + +# NOT-FOUND-2:[{"Address":"0x3b9aca00","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":0,"Discriminator":0,"FileName":"","FunctionName":"","Line":0,"Source":"","StartFileName":"","StartLine":0}]}] + +# 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 +# NO-INLINES:{"Address":"0x0","Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"} + +## Resolve valid address, no-inlines. +# NO-INLINES-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"main","Line":3,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2}]} + +## Invalid argument after a valid one, no-inlines. +# NO-INLINES-NEXT:{"Address":"0x0","Error":{"Message":"unable to parse arguments: some text2"},"ModuleName":"{{.*}}/Inputs/addr.exe"} + +# 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, inlines +# INLINE:{"Address":"0x0","Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"} + +## Resolve valid address, inlines. +# INLINE-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"inctwo","Line":3,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"inc","Line":7,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"main","Line":14,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12}]} + +## Invalid argument after a valid one, inlines. +# INLINE-NEXT:{"Address":"0x0","Error":{"Message":"unable to parse arguments: some text2"},"ModuleName":"{{.*}}/Inputs/addr.exe"} + +## Also check the last 3 test cases with llvm-adr2line. The expected result is the same but missing the FunctionName. +# RUN: llvm-addr2line --output-style=JSON -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, inlines +# INLINE-A2L:{"Address":"0x0","Error":{"Message":"unable to parse arguments: some text"},"ModuleName":"{{.*}}/Inputs/addr.exe"} + +## Resolve valid address, inlines. +# INLINE-A2L-NEXT:{"Address":"0x40054d","ModuleName":"{{.*}}/Inputs/addr.exe","Symbol":[{"Column":3,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"","Line":3,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"","Line":7,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6},{"Column":0,"Discriminator":0,"FileName":"/tmp{{/|\\\\}}x.c","FunctionName":"","Line":14,"Source":"","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12}]} + +## Invalid argument after a valid one, inlines. +# INLINE-A2L-NEXT:{"Address":"0x0","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,42 @@ +## 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:[{"Address":"0x0","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":"foo","Size":"0x4","Start":"0x0"},"ModuleName":"{{.*}}.o"}] + +## Test multiple addresses in the command line. +# RUN: llvm-symbolizer -e=%t.o "DATA 0" "DATA 0" --output-style=JSON | \ +# RUN: FileCheck %s --check-prefix=MULTI --strict-whitespace --match-full-lines --implicit-check-not={{.}} + +# MULTI:[{"Address":"0x0","Data":{"Name":"foo","Size":"0x4","Start":"0x0"},"ModuleName":"{{.*}}.o"},{"Address":"0x0","Data":{"Name":"foo","Size":"0x4","Start":"0x0"},"ModuleName":"{{.*}}.o"}] + +.data +.globl foo +.type foo, @object +.size foo, 4 +foo = . + 0x1100000000000000 +.4byte 1 diff --git a/llvm/test/tools/llvm-symbolizer/output-style-json-frame.test b/llvm/test/tools/llvm-symbolizer/output-style-json-frame.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-symbolizer/output-style-json-frame.test @@ -0,0 +1,211 @@ +## This test checks JSON output for FRAME. + +# REQUIRES: x86-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:[{"Address":"0x0","Error":{"Message":"unable to parse arguments: FRAME tmp.o Z"},"ModuleName":"tmp.o"}] + +# RUN: llvm-mc -filetype=obj -triple=i386-linux-gnu -o %t.o %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":"/tmp/test{{/|\\\\}}frame.cpp","DeclLine":2,"FrameOffset":-1,"FunctionName":"f","Name":"a","Size":"0x1","TagOffset":""},{"DeclFile":"/tmp/test{{/|\\\\}}frame.cpp","DeclLine":3,"FrameOffset":-8,"FunctionName":"f","Name":"b","Size":"0x4","TagOffset":""}],"ModuleName":"{{.*}}.o"}] + +## Generated from: +## +## void f() { +## char a; +## char *b; +## } +## +## clang++ --target=i386-linux-gnu frame.cpp -g -std=c++11 -S -o frame.s + + .text + .file "frame.cpp" + .globl _Z1fv # -- Begin function _Z1fv + .p2align 4, 0x90 + .type _Z1fv,@function +_Z1fv: # @_Z1fv +.Lfunc_begin0: + .file 1 "/tmp/test" "frame.cpp" + .loc 1 1 0 # frame.cpp:1:0 + .cfi_sections .debug_frame + .cfi_startproc +# %bb.0: # %entry + pushl %ebp + .cfi_def_cfa_offset 8 + .cfi_offset %ebp, -8 + movl %esp, %ebp + .cfi_def_cfa_register %ebp +.Ltmp0: + .loc 1 4 1 prologue_end # frame.cpp:4:1 + popl %ebp + .cfi_def_cfa %esp, 4 + retl +.Ltmp1: +.Lfunc_end0: + .size _Z1fv, .Lfunc_end0-_Z1fv + .cfi_endproc + # -- End function + .section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 37 # DW_AT_producer + .byte 14 # DW_FORM_strp + .byte 19 # DW_AT_language + .byte 5 # DW_FORM_data2 + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 16 # DW_AT_stmt_list + .byte 23 # DW_FORM_sec_offset + .byte 27 # DW_AT_comp_dir + .byte 14 # DW_FORM_strp + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 2 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 1 # DW_CHILDREN_yes + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 64 # DW_AT_frame_base + .byte 24 # DW_FORM_exprloc + .byte 110 # DW_AT_linkage_name + .byte 14 # DW_FORM_strp + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 3 # Abbreviation Code + .byte 52 # DW_TAG_variable + .byte 0 # DW_CHILDREN_no + .byte 2 # DW_AT_location + .byte 24 # DW_FORM_exprloc + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 4 # Abbreviation Code + .byte 36 # DW_TAG_base_type + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 62 # DW_AT_encoding + .byte 11 # DW_FORM_data1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 5 # Abbreviation Code + .byte 15 # DW_TAG_pointer_type + .byte 0 # DW_CHILDREN_no + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 # EOM(3) + .section .debug_info,"",@progbits +.Lcu_begin0: + .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit +.Ldebug_info_start0: + .short 4 # DWARF version number + .long .debug_abbrev # Offset Into Abbrev. Section + .byte 4 # Address Size (in bytes) + .byte 1 # Abbrev [1] 0xb:0x5a DW_TAG_compile_unit + .long .Linfo_string0 # DW_AT_producer + .short 26 # DW_AT_language + .long .Linfo_string1 # DW_AT_name + .long .Lline_table_start0 # DW_AT_stmt_list + .long .Linfo_string2 # DW_AT_comp_dir + .long .Lfunc_begin0 # DW_AT_low_pc + .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc + .byte 2 # Abbrev [2] 0x26:0x32 DW_TAG_subprogram + .long .Lfunc_begin0 # DW_AT_low_pc + .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 85 + .long .Linfo_string3 # DW_AT_linkage_name + .long .Linfo_string4 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 1 # DW_AT_decl_line + # DW_AT_external + .byte 3 # Abbrev [3] 0x3b:0xe DW_TAG_variable + .byte 2 # DW_AT_location + .byte 145 + .byte 127 + .long .Linfo_string5 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 2 # DW_AT_decl_line + .long 88 # DW_AT_type + .byte 3 # Abbrev [3] 0x49:0xe DW_TAG_variable + .byte 2 # DW_AT_location + .byte 145 + .byte 120 + .long .Linfo_string7 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 3 # DW_AT_decl_line + .long 95 # DW_AT_type + .byte 0 # End Of Children Mark + .byte 4 # Abbrev [4] 0x58:0x7 DW_TAG_base_type + .long .Linfo_string6 # DW_AT_name + .byte 6 # DW_AT_encoding + .byte 1 # DW_AT_byte_size + .byte 5 # Abbrev [5] 0x5f:0x5 DW_TAG_pointer_type + .long 88 # DW_AT_type + .byte 0 # End Of Children Mark +.Ldebug_info_end0: + .section .debug_str,"MS",@progbits,1 +.Linfo_string0: + .asciz "clang version 13.0.0" # string offset=0 +.Linfo_string1: + .asciz "frame.cpp" # string offset=105 +.Linfo_string2: + .asciz "/tmp/test" # string offset=115 +.Linfo_string3: + .asciz "_Z1fv" # string offset=140 +.Linfo_string4: + .asciz "f" # string offset=146 +.Linfo_string5: + .asciz "a" # string offset=148 +.Linfo_string6: + .asciz "char" # string offset=150 +.Linfo_string7: + .asciz "b" # string offset=155 + .addrsig + .section .debug_line,"",@progbits +.Lline_table_start0: 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, Offset}, 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.Pretty); 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;