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 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"} + +## 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 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 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.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,412 @@ +## 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: llvm-mc -filetype=obj -triple=aarch64-linux-android -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{{/|\\\\}}frame.c","DeclLine":5,"FrameOffset":-16,"FunctionName":"f","Name":"b","Size":"0x10","TagOffset":"0x0"},{"DeclFile":"/tmp{{/|\\\\}}frame.c","DeclLine":6,"FrameOffset":-48,"FunctionName":"f","Name":"c","Size":"0x20","TagOffset":"0x80"},{"DeclFile":"/tmp{{/|\\\\}}frame.c","DeclLine":4,"FunctionName":"f","Name":"a","Size":"0x8","TagOffset":""}],"ModuleName":"{{.*}}.o"}] + +## Generated by clang version 12.0.0 from: +## +## void g(void *x, void *y, void *z); +## +## void f() { +## char *a; +## char b[16]; +## char c[32]; +## g(a,b,c); +## } +## +## clang --target=aarch64-linux-android -fsanitize=hwaddress -fsanitize-hwaddress-abi=platform frame.c -O -g -S -o frame.s + + .text + .file "frame.c" + .globl f // -- Begin function f + .p2align 2 + .type f,@function +f: // @f +.Lfunc_begin0: + .file 1 "/tmp" "frame.c" + .loc 1 3 0 // frame.c:3:0 + .cfi_startproc +// %bb.0: + sub sp, sp, #96 // =96 + stp x29, x30, [sp, #48] // 16-byte Folded Spill + str x21, [sp, #64] // 8-byte Folded Spill + stp x20, x19, [sp, #80] // 16-byte Folded Spill + add x29, sp, #48 // =48 + .cfi_def_cfa w29, 48 + .cfi_offset w19, -8 + .cfi_offset w20, -16 + .cfi_offset w21, -32 + .cfi_offset w30, -40 + .cfi_offset w29, -48 + mrs x8, TPIDR_EL0 + ldr x9, [x8, #48] + adr x10, #0 + orr x10, x10, x29, lsl #44 + asr x11, x9, #3 + asr x12, x9, #56 + orr x13, x9, #0xffffffff + str x10, [x9], #8 + bic x9, x9, x12, lsl #12 + mov x12, sp + str x9, [x8, #48] +.Ltmp0: + .loc 1 5 3 prologue_end // frame.c:5:3 + eor x8, x11, #0x80 + sub x10, x29, #16 // =16 + orr x2, x12, x8, lsl #56 + and w8, w8, #0xff + lsr x19, x10, #4 + lsr x20, x12, #4 + add x21, x13, #1 // =1 + orr x1, x10, x11, lsl #56 + bfi w8, w8, #8, #8 + strb w11, [x21, x19] + strh w8, [x21, x20] + .loc 1 7 3 // frame.c:7:3 + bl g +.Ltmp1: + .loc 1 8 1 // frame.c:8:1 + strb wzr, [x21, x19] + strh wzr, [x21, x20] + ldp x20, x19, [sp, #80] // 16-byte Folded Reload + ldr x21, [sp, #64] // 8-byte Folded Reload + ldp x29, x30, [sp, #48] // 16-byte Folded Reload + add sp, sp, #96 // =96 + ret +.Ltmp2: +.Lfunc_end0: + .size f, .Lfunc_end0-f + .cfi_endproc + // -- End function + .section .text.hwasan.module_ctor,"axG",@progbits,hwasan.module_ctor,comdat + .p2align 2 // -- Begin function hwasan.module_ctor + .type hwasan.module_ctor,@function +hwasan.module_ctor: // @hwasan.module_ctor +.Lfunc_begin1: + .cfi_startproc +// %bb.0: + str x30, [sp, #-16]! // 8-byte Folded Spill + .cfi_def_cfa_offset 16 + .cfi_offset w30, -16 + bl __hwasan_init + ldr x30, [sp], #16 // 8-byte Folded Reload + ret +.Lfunc_end1: + .size hwasan.module_ctor, .Lfunc_end1-hwasan.module_ctor + .cfi_endproc + // -- End function + .section .init_array.0,"aGw",@init_array,hwasan.module_ctor,comdat + .p2align 3 + .xword hwasan.module_ctor + .hidden __start_hwasan_globals + .hidden __stop_hwasan_globals + .type .Lhwasan.note,@object // @hwasan.note + .section .note.hwasan.globals,"aG",@note,hwasan.module_ctor,comdat + .p2align 2 +.Lhwasan.note: + .word 8 // 0x8 + .word 8 // 0x8 + .word 3 // 0x3 + .asciz "LLVM\000\000\000" + .word __start_hwasan_globals-.Lhwasan.note + .word __stop_hwasan_globals-.Lhwasan.note + .size .Lhwasan.note, 28 + + .type .Lhwasan.dummy.global,@object // @hwasan.dummy.global + .section hwasan_globals,"aGo",@progbits,hwasan.module_ctor,comdat,.Lhwasan.note,unique,1 + .p2align 2 +.Lhwasan.dummy.global: + .size .Lhwasan.dummy.global, 0 + + .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 + .ascii "\227B" // DW_AT_GNU_all_call_sites + .byte 25 // DW_FORM_flag_present + .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 + .ascii "\203|" // DW_AT_LLVM_tag_offset + .byte 11 // DW_FORM_data1 + .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 52 // DW_TAG_variable + .byte 0 // DW_CHILDREN_no + .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 5 // Abbreviation Code + .ascii "\211\202\001" // DW_TAG_GNU_call_site + .byte 0 // DW_CHILDREN_no + .byte 49 // DW_AT_abstract_origin + .byte 19 // DW_FORM_ref4 + .byte 17 // DW_AT_low_pc + .byte 1 // DW_FORM_addr + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 6 // Abbreviation Code + .byte 46 // DW_TAG_subprogram + .byte 1 // DW_CHILDREN_yes + .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 39 // DW_AT_prototyped + .byte 25 // DW_FORM_flag_present + .byte 60 // DW_AT_declaration + .byte 25 // DW_FORM_flag_present + .byte 63 // DW_AT_external + .byte 25 // DW_FORM_flag_present + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 7 // Abbreviation Code + .byte 5 // DW_TAG_formal_parameter + .byte 0 // DW_CHILDREN_no + .byte 73 // DW_AT_type + .byte 19 // DW_FORM_ref4 + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 8 // Abbreviation Code + .byte 15 // DW_TAG_pointer_type + .byte 0 // DW_CHILDREN_no + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 9 // Abbreviation Code + .byte 1 // DW_TAG_array_type + .byte 1 // DW_CHILDREN_yes + .byte 73 // DW_AT_type + .byte 19 // DW_FORM_ref4 + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 10 // Abbreviation Code + .byte 33 // DW_TAG_subrange_type + .byte 0 // DW_CHILDREN_no + .byte 73 // DW_AT_type + .byte 19 // DW_FORM_ref4 + .byte 55 // DW_AT_count + .byte 11 // DW_FORM_data1 + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 11 // 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 12 // Abbreviation Code + .byte 36 // DW_TAG_base_type + .byte 0 // DW_CHILDREN_no + .byte 3 // DW_AT_name + .byte 14 // DW_FORM_strp + .byte 11 // DW_AT_byte_size + .byte 11 // DW_FORM_data1 + .byte 62 // DW_AT_encoding + .byte 11 // DW_FORM_data1 + .byte 0 // EOM(1) + .byte 0 // EOM(2) + .byte 13 // 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: + .word .Ldebug_info_end0-.Ldebug_info_start0 // Length of Unit +.Ldebug_info_start0: + .hword 4 // DWARF version number + .word .debug_abbrev // Offset Into Abbrev. Section + .byte 8 // Address Size (in bytes) + .byte 1 // Abbrev [1] 0xb:0xaf DW_TAG_compile_unit + .word .Linfo_string0 // DW_AT_producer + .hword 12 // DW_AT_language + .word .Linfo_string1 // DW_AT_name + .word .Lline_table_start0 // DW_AT_stmt_list + .word .Linfo_string2 // DW_AT_comp_dir + .xword .Lfunc_begin0 // DW_AT_low_pc + .word .Lfunc_end0-.Lfunc_begin0 // DW_AT_high_pc + .byte 2 // Abbrev [2] 0x2a:0x4c DW_TAG_subprogram + .xword .Lfunc_begin0 // DW_AT_low_pc + .word .Lfunc_end0-.Lfunc_begin0 // DW_AT_high_pc + .byte 1 // DW_AT_frame_base + .byte 109 + // DW_AT_GNU_all_call_sites + .word .Linfo_string4 // DW_AT_name + .byte 1 // DW_AT_decl_file + .byte 3 // DW_AT_decl_line + // DW_AT_external + .byte 3 // Abbrev [3] 0x3f:0xf DW_TAG_variable + .byte 2 // DW_AT_location + .byte 145 + .byte 112 + .byte 0 // DW_AT_LLVM_tag_offset + .word .Linfo_string5 // DW_AT_name + .byte 1 // DW_AT_decl_file + .byte 5 // DW_AT_decl_line + .word 142 // DW_AT_type + .byte 3 // Abbrev [3] 0x4e:0xf DW_TAG_variable + .byte 2 // DW_AT_location + .byte 145 + .byte 80 + .byte 128 // DW_AT_LLVM_tag_offset + .word .Linfo_string8 // DW_AT_name + .byte 1 // DW_AT_decl_file + .byte 6 // DW_AT_decl_line + .word 168 // DW_AT_type + .byte 4 // Abbrev [4] 0x5d:0xb DW_TAG_variable + .word .Linfo_string9 // DW_AT_name + .byte 1 // DW_AT_decl_file + .byte 4 // DW_AT_decl_line + .word 180 // DW_AT_type + .byte 5 // Abbrev [5] 0x68:0xd DW_TAG_GNU_call_site + .word 118 // DW_AT_abstract_origin + .xword .Ltmp1 // DW_AT_low_pc + .byte 0 // End Of Children Mark + .byte 6 // Abbrev [6] 0x76:0x17 DW_TAG_subprogram + .word .Linfo_string3 // DW_AT_name + .byte 1 // DW_AT_decl_file + .byte 1 // DW_AT_decl_line + // DW_AT_prototyped + // DW_AT_declaration + // DW_AT_external + .byte 7 // Abbrev [7] 0x7d:0x5 DW_TAG_formal_parameter + .word 141 // DW_AT_type + .byte 7 // Abbrev [7] 0x82:0x5 DW_TAG_formal_parameter + .word 141 // DW_AT_type + .byte 7 // Abbrev [7] 0x87:0x5 DW_TAG_formal_parameter + .word 141 // DW_AT_type + .byte 0 // End Of Children Mark + .byte 8 // Abbrev [8] 0x8d:0x1 DW_TAG_pointer_type + .byte 9 // Abbrev [9] 0x8e:0xc DW_TAG_array_type + .word 154 // DW_AT_type + .byte 10 // Abbrev [10] 0x93:0x6 DW_TAG_subrange_type + .word 161 // DW_AT_type + .byte 16 // DW_AT_count + .byte 0 // End Of Children Mark + .byte 11 // Abbrev [11] 0x9a:0x7 DW_TAG_base_type + .word .Linfo_string6 // DW_AT_name + .byte 8 // DW_AT_encoding + .byte 1 // DW_AT_byte_size + .byte 12 // Abbrev [12] 0xa1:0x7 DW_TAG_base_type + .word .Linfo_string7 // DW_AT_name + .byte 8 // DW_AT_byte_size + .byte 7 // DW_AT_encoding + .byte 9 // Abbrev [9] 0xa8:0xc DW_TAG_array_type + .word 154 // DW_AT_type + .byte 10 // Abbrev [10] 0xad:0x6 DW_TAG_subrange_type + .word 161 // DW_AT_type + .byte 32 // DW_AT_count + .byte 0 // End Of Children Mark + .byte 13 // Abbrev [13] 0xb4:0x5 DW_TAG_pointer_type + .word 154 // DW_AT_type + .byte 0 // End Of Children Mark +.Ldebug_info_end0: + .section .debug_str,"MS",@progbits,1 +.Linfo_string0: + .asciz "clang version 12.0.0" // string offset=0 +.Linfo_string1: + .asciz "frame.c" // string offset=21 +.Linfo_string2: + .asciz "/tmp" // string offset=29 +.Linfo_string3: + .asciz "g" // string offset=55 +.Linfo_string4: + .asciz "f" // string offset=57 +.Linfo_string5: + .asciz "b" // string offset=59 +.Linfo_string6: + .asciz "char" // string offset=61 +.Linfo_string7: + .asciz "__ARRAY_SIZE_TYPE__" // string offset=66 +.Linfo_string8: + .asciz "c" // string offset=86 +.Linfo_string9: + .asciz "a" // string offset=88 + .ident "clang version 12.0.0" + .section ".note.GNU-stack","",@progbits + .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, 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;