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,17 @@ $ 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 + [{"ModuleName":"inlined.elf","Address":"0x4004be","Error":{"Code":0},"DIInliningInfo":{"Frames":[ + {"FunctionName":"baz()","StartFileName":"/tmp/test.cpp","StartLine":9,"FileName":"/tmp/test.cpp","Line":11,"Column":18}, + {"FunctionName":"main","StartFileName":"/tmp/test.cpp","StartLine":14,"FileName":"/tmp/test.cpp","Line":15}]}}, + {"ModuleName":"inlined.elf","Address":"0x400486","Error":{"Code":0},"DIInliningInfo":{"Frames":[ + {"FunctionName":"foo()","StartFileName":"/tmp/test.cpp","StartLine":5,"FileName":"/tmp/test.cpp","Line": 6,"Column": 3}]}}] + + $ llvm-symbolizer --output-style=JSON --obj=inlined.elf 0x4004be --no-inlines + {"ModuleName":"inlined.elf","Address":"0x4004be","Error":{"Code":0},"DILineInfo": + {"FunctionName":"main","StartFileName":"/tmp/test.cpp","StartLine":14,"FileName":"/tmp/test.cpp","Line":11,"Column":18}} + .. 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 @@ -14,7 +14,9 @@ #ifndef LLVM_DEBUGINFO_SYMBOLIZE_DIPRINTER_H #define LLVM_DEBUGINFO_SYMBOLIZE_DIPRINTER_H +#include "llvm/Support/ErrorHandling.h" #include +#include namespace llvm { struct DILineInfo; @@ -25,34 +27,88 @@ namespace symbolize { +template struct DICommon { + std::string ModuleName; + uint64_t Address = 0; + int ErrorCode = 0; + const T &Result; + DICommon(const std::string &ObjFile, uint64_t Address, const T &Result, + int ErrorCode = 0) + : ModuleName(ObjFile), Address(Address), Result(Result), + ErrorCode(ErrorCode){}; +}; + class DIPrinter { public: - enum class OutputStyle { LLVM, GNU }; + enum class OutputStyle { LLVM, GNU, JSON }; -private: +protected: raw_ostream &OS; + OutputStyle Style; + +public: + DIPrinter(raw_ostream &OS, OutputStyle Style = OutputStyle::LLVM) + : OS(OS), Style(Style) {} + virtual ~DIPrinter(){}; + + virtual void print(const DICommon &Common) = 0; + virtual void print(const DICommon &Common) = 0; + virtual void print(const DICommon &Common) = 0; + virtual void print(const DICommon> &Common) = 0; + + // Print only the error. + virtual void print(const DICommon &Common) = 0; +}; + +class DIPrinterGeneric : public DIPrinter { +private: + bool PrintAddress; bool PrintFunctionNames; bool PrintPretty; int PrintSourceContext; bool Verbose; - OutputStyle Style; void print(const DILineInfo &Info, bool Inlined); + void printAddress(uint64_t Address); void printContext(const std::string &FileName, int64_t Line); public: - DIPrinter(raw_ostream &OS, bool PrintFunctionNames = true, - bool PrintPretty = false, int PrintSourceContext = 0, - bool Verbose = false, OutputStyle Style = OutputStyle::LLVM) - : OS(OS), PrintFunctionNames(PrintFunctionNames), - PrintPretty(PrintPretty), PrintSourceContext(PrintSourceContext), - Verbose(Verbose), Style(Style) {} - - DIPrinter &operator<<(const DILineInfo &Info); - DIPrinter &operator<<(const DIInliningInfo &Info); - DIPrinter &operator<<(const DIGlobal &Global); - DIPrinter &operator<<(const DILocal &Local); + DIPrinterGeneric(raw_ostream &OS, OutputStyle Style, bool PrintAddress, + bool PrintFunctionNames = true, bool PrintPretty = false, + int PrintSourceContext = 0, bool Verbose = false) + : DIPrinter(OS, Style), PrintAddress(PrintAddress), + PrintFunctionNames(PrintFunctionNames), PrintPretty(PrintPretty), + PrintSourceContext(PrintSourceContext), Verbose(Verbose) {} + + void print(const DICommon &Common); + void print(const DICommon &Common); + void print(const DICommon &Common); + void print(const DICommon> &Common); + + // Print only the error. + void print(const DICommon& Common) { + llvm_unreachable("Method not implemented"); + } }; + +class DIPrinterJSON : public DIPrinter { +private: + void print(const std::string &ObjFile, uint64_t Address, int EC, + const std::string &ErrorMsg); + +public: + DIPrinterJSON(raw_ostream &OS) + : DIPrinter(OS, DIPrinter::OutputStyle::JSON) {} + + void print(const DICommon &Common); + void print(const DICommon &Common); + void print(const DICommon &Common); + void print(const DICommon> &Common); + + // Print only the error. + void print(const DICommon &Common); +}; + } } 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 @@ -16,6 +16,7 @@ #include "llvm/DebugInfo/DIContext.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/Format.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" @@ -31,7 +32,7 @@ namespace symbolize { // Prints source code around in the FileName the Line. -void DIPrinter::printContext(const std::string &FileName, int64_t Line) { +void DIPrinterGeneric::printContext(const std::string &FileName, int64_t Line) { if (PrintSourceContext <= 0) return; @@ -60,7 +61,7 @@ } } -void DIPrinter::print(const DILineInfo &Info, bool Inlined) { +void DIPrinterGeneric::print(const DILineInfo &Info, bool Inlined) { if (PrintFunctionNames) { std::string FunctionName = Info.FunctionName; if (FunctionName == DILineInfo::BadString) @@ -94,63 +95,223 @@ OS << " Discriminator: " << Info.Discriminator << "\n"; } -DIPrinter &DIPrinter::operator<<(const DILineInfo &Info) { - print(Info, false); - return *this; +void DIPrinterGeneric::printAddress(uint64_t Address) { + if (PrintAddress) { + OS << "0x"; + OS.write_hex(Address); + StringRef Delimiter = PrintPretty ? ": " : "\n"; + OS << Delimiter; + } +} + +void DIPrinterGeneric::print(const DICommon &Common) { + printAddress(Common.Address); + print(Common.Result, false); } -DIPrinter &DIPrinter::operator<<(const DIInliningInfo &Info) { - uint32_t FramesNum = Info.getNumberOfFrames(); +void DIPrinterGeneric::print(const DICommon &Common) { + printAddress(Common.Address); + uint32_t FramesNum = Common.Result.getNumberOfFrames(); if (FramesNum == 0) { print(DILineInfo(), false); - return *this; + return; } - for (uint32_t i = 0; i < FramesNum; i++) - print(Info.getFrame(i), i > 0); - return *this; + for (uint32_t I = 0; I < FramesNum; ++I) + print(Common.Result.getFrame(I), I > 0); } -DIPrinter &DIPrinter::operator<<(const DIGlobal &Global) { - std::string Name = Global.Name; +void DIPrinterGeneric::print(const DICommon &Common) { + printAddress(Common.Address); + std::string Name = Common.Result.Name; if (Name == DILineInfo::BadString) Name = DILineInfo::Addr2LineBadString; OS << Name << "\n"; - OS << Global.Start << " " << Global.Size << "\n"; - return *this; + OS << Common.Result.Start << " " << Common.Result.Size << "\n"; +} + +void DIPrinterGeneric::print(const DICommon> &Common) { + printAddress(Common.Address); + if (Common.Result.empty()) { + outs() << "??\n"; + return; + } + for (const DILocal &Local : Common.Result) { + if (Local.FunctionName.empty()) + OS << "??\n"; + else + OS << Local.FunctionName << '\n'; + + if (Local.Name.empty()) + OS << "??\n"; + else + OS << Local.Name << '\n'; + + if (Local.DeclFile.empty()) + OS << "??"; + else + OS << Local.DeclFile; + OS << ':' << Local.DeclLine << '\n'; + + if (Local.FrameOffset) + OS << *Local.FrameOffset << ' '; + else + OS << "?? "; + + if (Local.Size) + OS << *Local.Size << ' '; + else + OS << "?? "; + + if (Local.TagOffset) + OS << *Local.TagOffset << '\n'; + else + OS << "??\n"; + } +} + +//--------------------------------------------------------------------------------------------------------- + +std::string toHex(uint64_t V) { return ("0x" + Twine::utohexstr(V)).str(); } + +static void toJSON(json::OStream &J, const DILineInfo &Info) { + J.objectBegin(); + if (Info.Source) + J.attribute("Source", *Info.Source); + if (Info.FunctionName != DILineInfo::BadString) + J.attribute("FunctionName", Info.FunctionName); + if (Info.StartFileName != DILineInfo::BadString) + J.attribute("StartFileName", Info.StartFileName); + if (Info.StartLine) + J.attribute("StartLine", Info.StartLine); + if (Info.FileName != DILineInfo::BadString) + J.attribute("FileName", Info.FileName); + // Print only valid line and column to reduce noise in the output + if (Info.Line) + J.attribute("Line", Info.Line); + if (Info.Column) + J.attribute("Column", Info.Column); + if (Info.Discriminator) + J.attribute("Discriminator", Info.Discriminator); + J.objectEnd(); +} + +void DIPrinterJSON::print(const DICommon &Common) { + { + json::OStream J(OS); + J.objectBegin(); + if (!Common.ModuleName.empty()) + J.attribute("ModuleName", Common.ModuleName); + J.attribute("Address", toHex(Common.Address)); + assert(Common.ErrorCode == 0); + J.attributeObject("Error", [&] { J.attribute("Code", Common.ErrorCode); }); + J.attributeBegin("DILineInfo"); + toJSON(J, Common.Result); + J.attributeEnd(); + J.objectEnd(); + } + OS << '\n'; +} + +void DIPrinterJSON::print(const DICommon &Common) { + { + json::OStream J(OS); + J.objectBegin(); + if (!Common.ModuleName.empty()) + J.attribute("ModuleName", Common.ModuleName); + J.attribute("Address", toHex(Common.Address)); + assert(Common.ErrorCode == 0); + J.attributeObject("Error", [&] { J.attribute("Code", Common.ErrorCode); }); + J.attributeBegin("DIInliningInfo"); + J.objectBegin(); + J.attributeBegin("Frames"); + J.arrayBegin(); + uint32_t FramesNum = Common.Result.getNumberOfFrames(); + for (uint32_t I = 0; I < FramesNum; ++I) { + OS << '\n'; + toJSON(J, Common.Result.getFrame(I)); + } + J.arrayEnd(); + J.attributeEnd(); // Frames + J.objectEnd(); + J.attributeEnd(); // DIInliningInfo + J.objectEnd(); + } + OS << '\n'; } -DIPrinter &DIPrinter::operator<<(const DILocal &Local) { - if (Local.FunctionName.empty()) - OS << "??\n"; - else - OS << Local.FunctionName << '\n'; - - if (Local.Name.empty()) - OS << "??\n"; - else - OS << Local.Name << '\n'; - - if (Local.DeclFile.empty()) - OS << "??"; - else - OS << Local.DeclFile; - OS << ':' << Local.DeclLine << '\n'; - - if (Local.FrameOffset) - OS << *Local.FrameOffset << ' '; - else - OS << "?? "; - - if (Local.Size) - OS << *Local.Size << ' '; - else - OS << "?? "; - - if (Local.TagOffset) - OS << *Local.TagOffset << '\n'; - else - OS << "??\n"; - return *this; +void DIPrinterJSON::print(const DICommon &Common) { + { + json::OStream J(OS); + J.objectBegin(); + if (!Common.ModuleName.empty()) + J.attribute("ModuleName", Common.ModuleName); + J.attribute("Address", toHex(Common.Address)); + assert(Common.ErrorCode == 0); + J.attributeObject("Error", [&] { J.attribute("Code", Common.ErrorCode); }); + J.attributeBegin("DIGlobal"); + J.objectBegin(); + if (Common.Result.Name != DILineInfo::BadString) + J.attribute("Name", Common.Result.Name); + J.attribute("Start", toHex(Common.Result.Start)); + J.attribute("Size", toHex(Common.Result.Size)); + J.objectEnd(); + J.attributeEnd(); + J.objectEnd(); + } + OS << '\n'; +} + +void DIPrinterJSON::print(const DICommon> &Common) { + { + json::OStream J(OS); + J.objectBegin(); + if (!Common.ModuleName.empty()) + J.attribute("ModuleName", Common.ModuleName); + J.attribute("Address", toHex(Common.Address)); + assert(Common.ErrorCode == 0); + J.attributeObject("Error", [&] { J.attribute("Code", Common.ErrorCode); }); + J.attributeBegin("vector_DILocal"); + J.arrayBegin(); + for (const DILocal &L : Common.Result) { + OS << '\n'; + J.objectBegin(); + if (!L.FunctionName.empty()) + J.attribute("FunctionName", L.FunctionName); + if (!L.Name.empty()) + J.attribute("Name", L.Name); + if (!L.DeclFile.empty()) + J.attribute("DeclFile", L.DeclFile); + J.attribute("DeclLine", int64_t(L.DeclLine)); + if (L.FrameOffset) + J.attribute("FrameOffset", *L.FrameOffset); + if (L.Size) + J.attribute("Size", toHex(*L.Size)); + if (L.TagOffset) + J.attribute("TagOffset", toHex(*L.TagOffset)); + J.objectEnd(); + } + J.arrayEnd(); + J.attributeEnd(); + J.objectEnd(); + } + OS << '\n'; +} + +void DIPrinterJSON::print(const DICommon &Common) { + { + json::OStream J(OS); + J.objectBegin(); + if (!Common.ModuleName.empty()) + J.attribute("ModuleName", Common.ModuleName); + J.attribute("Address", toHex(Common.Address)); + assert(Common.ErrorCode != 0); + J.attributeObject("Error", [&] { + J.attribute("Code", Common.ErrorCode); + J.attribute("Message", Common.Result); + }); + J.objectEnd(); + } + OS << '\n'; } } // end namespace symbolize 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,67 @@ +## 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:{"ModuleName":"{{.*}}/no-file.exe","Address":"0x0","Error":{"Code":2,"Message":"[[MSG]]"}} + +## Resolve out of range address, no-inlines. Expected empty object, as all the default values are omitted. +# 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:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x3b9aca00","Error":{"Code":0},"DILineInfo":{}} + +## Resolve out of range address, inlines. Expected a Frames list with one empty object, as all the default values are omitted. +# 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:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x3b9aca00","Error":{"Code":0},"DIInliningInfo":{"Frames":[ +# NOT-FOUND-2-NEXT:{}]}} + +# 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:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: some text"}} + +## Resolve valid address, no-inlines. +# NO-INLINES-NEXT:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x40054d","Error":{"Code":0},"DILineInfo":{"FunctionName":"main","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2,"FileName":"/tmp{{/|\\\\}}x.c","Line":3,"Column":3}} + +## Invalid argument after a valid one, no-inlines. +# NO-INLINES-NEXT:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: some text2"}} + +# 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:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: some text"}} + +## Resolve valid address, inlines. +# INLINE-NEXT:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x40054d","Error":{"Code":0},"DIInliningInfo":{"Frames":[ +# INLINE-NEXT:{"FunctionName":"inctwo","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2,"FileName":"/tmp{{/|\\\\}}x.c","Line":3,"Column":3} +# INLINE-NEXT:,{"FunctionName":"inc","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6,"FileName":"/tmp{{/|\\\\}}x.c","Line":7} +# INLINE-NEXT:,{"FunctionName":"main","StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12,"FileName":"/tmp{{/|\\\\}}x.c","Line":14}]}} + +## Invalid argument after a valid one, inlines. +# INLINE-NEXT:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: some text2"}} + +## 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:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: some text"}} + +## Resolve valid address, inlines. +# INLINE-A2L-NEXT:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x40054d","Error":{"Code":0},"DIInliningInfo":{"Frames":[ +# INLINE-A2L-NEXT:{"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":2,"FileName":"/tmp{{/|\\\\}}x.c","Line":3,"Column":3} +# INLINE-A2L-NEXT:,{"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":6,"FileName":"/tmp{{/|\\\\}}x.c","Line":7} +# INLINE-A2L-NEXT:,{"StartFileName":"/tmp{{/|\\\\}}x.c","StartLine":12,"FileName":"/tmp{{/|\\\\}}x.c","Line":14}]}} + +## Invalid argument after a valid one, inlines. +# INLINE-A2L-NEXT:{"ModuleName":"{{.*}}/Inputs/addr.exe","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: some text2"}} 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,44 @@ +## 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:{"ModuleName":"{{.*}}no-file.o","Address":"0x0","Error":{"Code":2,"Message":"[[MSG]]"}} + +## 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:{"ModuleName":"tmp.o","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: DATA tmp.o Z"}} + +# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o + +## Resolve out of range address. Only Start and Size is expected. +# 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:{"ModuleName":"{{.*}}.o","Address":"0x3b9aca00","Error":{"Code":0},"DIGlobal":{"Start":"0x0","Size":"0x0"}} + +## 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:{"ModuleName":"{{.*}}.o","Address":"0x0","Error":{"Code":0},"DIGlobal":{"Name":"foo","Start":"0x0","Size":"0x4"}} + +## 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:[{"ModuleName":"{{.*}}.o","Address":"0x0","Error":{"Code":0},"DIGlobal":{"Name":"foo","Start":"0x0","Size":"0x4"}} +# MULTI-NEXT:,{"ModuleName":"{{.*}}.o","Address":"0x0","Error":{"Code":0},"DIGlobal":{"Name":"foo","Start":"0x0","Size":"0x4"}} +# MULTI-NEXT:] + +.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,213 @@ +## 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:{"ModuleName":"{{.*}}no-file.o","Address":"0x0","Error":{"Code":2,"Message":"[[MSG]]"}} + +## 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:{"ModuleName":"tmp.o","Address":"0x0","Error":{"Code":22,"Message":"unable to parse arguments: FRAME tmp.o Z"}} + +# 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:{"ModuleName":"{{.*}}.o","Address":"0x3b9aca00","Error":{"Code":0},"vector_DILocal":[]} + +## 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:{"ModuleName":"{{.*}}.o","Address":"0x0","Error":{"Code":0},"vector_DILocal":[ +# CHECK-NEXT:{"FunctionName":"f","Name":"a","DeclFile":"/tmp/test{{/|\\\\}}frame.cpp","DeclLine":2,"FrameOffset":-1,"Size":"0x1"} +# CHECK-NEXT:,{"FunctionName":"f","Name":"b","DeclFile":"/tmp/test{{/|\\\\}}frame.cpp","DeclLine":3,"FrameOffset":-8,"Size":"0x4"}]} + +## 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 @@ -83,6 +83,33 @@ return true; } +template +static void printResOrErr(Expected &ResOrErr, const std::string &ModuleName, + uint64_t Address, DIPrinter::OutputStyle OutputStyle, + DIPrinter &Printer) { + if (ResOrErr) { + // No error, print the result. + Printer.print(DICommon(ModuleName, Address, *ResOrErr)); + return; + } + + // Handle the error. + if (OutputStyle == DIPrinter::OutputStyle::JSON) { + handleAllErrors( + std::move(ResOrErr.takeError()), [&](const ErrorInfoBase &EI) { + Printer.print(DICommon( + ModuleName, Address, EI.message(), EI.convertToErrorCode().value())); + }); + return; + } + + error(ResOrErr); + + // Print an empty struct in case of Cmd != Command::Frame + if (!std::is_same>::value) + Printer.print(DICommon(ModuleName, Address, T())); +} + enum class Command { Code, Data, @@ -144,34 +171,29 @@ uint64_t Offset = 0; if (!parseCommand(Args.getLastArgValue(OPT_obj_EQ), IsAddr2Line, StringRef(InputString), Cmd, ModuleName, Offset)) { - outs() << InputString << "\n"; + if (OutputStyle == DIPrinter::OutputStyle::JSON) + Printer.print(DICommon( + ModuleName, Offset, + ("unable to parse arguments: " + InputString).str(), + int(std::errc::invalid_argument))); + else + outs() << InputString << "\n"; return; } - if (Args.hasArg(OPT_addresses)) { - outs() << "0x"; - outs().write_hex(Offset); - StringRef Delimiter = Args.hasArg(OPT_pretty_print) ? ": " : "\n"; - outs() << Delimiter; - } - Offset -= AdjustVMA; + uint64_t AdjustedOffset = Offset - AdjustVMA; if (Cmd == Command::Data) { - auto ResOrErr = Symbolizer.symbolizeData( - ModuleName, {Offset, object::SectionedAddress::UndefSection}); - Printer << (error(ResOrErr) ? DIGlobal() : ResOrErr.get()); + Expected ResOrErr = Symbolizer.symbolizeData( + ModuleName, {AdjustedOffset, object::SectionedAddress::UndefSection}); + printResOrErr(ResOrErr, ModuleName, Offset, OutputStyle, Printer); } else if (Cmd == Command::Frame) { - auto ResOrErr = Symbolizer.symbolizeFrame( - ModuleName, {Offset, object::SectionedAddress::UndefSection}); - if (!error(ResOrErr)) { - for (DILocal Local : *ResOrErr) - Printer << Local; - if (ResOrErr->empty()) - outs() << "??\n"; - } + Expected> ResOrErr = Symbolizer.symbolizeFrame( + ModuleName, {AdjustedOffset, object::SectionedAddress::UndefSection}); + printResOrErr(ResOrErr, ModuleName, Offset, OutputStyle, Printer); } else if (Args.hasFlag(OPT_inlines, OPT_no_inlines, !IsAddr2Line)) { - auto ResOrErr = Symbolizer.symbolizeInlinedCode( - ModuleName, {Offset, object::SectionedAddress::UndefSection}); - Printer << (error(ResOrErr) ? DIInliningInfo() : ResOrErr.get()); + Expected ResOrErr = Symbolizer.symbolizeInlinedCode( + ModuleName, {AdjustedOffset, object::SectionedAddress::UndefSection}); + printResOrErr(ResOrErr, ModuleName, Offset, OutputStyle, Printer); } else if (OutputStyle == DIPrinter::OutputStyle::GNU) { // With PrintFunctions == FunctionNameKind::LinkageName (default) // and UseSymbolTable == true (also default), Symbolizer.symbolizeCode() @@ -179,18 +201,18 @@ // caller function in the inlining chain. This contradicts the existing // behavior of addr2line. Symbolizer.symbolizeInlinedCode() overrides only // the topmost function, which suits our needs better. - auto ResOrErr = Symbolizer.symbolizeInlinedCode( - ModuleName, {Offset, object::SectionedAddress::UndefSection}); + Expected ResOrErr = Symbolizer.symbolizeInlinedCode( + ModuleName, {AdjustedOffset, object::SectionedAddress::UndefSection}); if (!ResOrErr || ResOrErr->getNumberOfFrames() == 0) { error(ResOrErr); - Printer << DILineInfo(); - } else { - Printer << ResOrErr->getFrame(0); - } + Printer.print(DICommon(ModuleName, Offset, DILineInfo())); + } else + Printer.print( + DICommon(ModuleName, Offset, ResOrErr->getFrame(0))); } else { - auto ResOrErr = Symbolizer.symbolizeCode( - ModuleName, {Offset, object::SectionedAddress::UndefSection}); - Printer << (error(ResOrErr) ? DILineInfo() : ResOrErr.get()); + Expected ResOrErr = Symbolizer.symbolizeCode( + ModuleName, {AdjustedOffset, object::SectionedAddress::UndefSection}); + printResOrErr(ResOrErr, ModuleName, Offset, OutputStyle, Printer); } if (OutputStyle == DIPrinter::OutputStyle::LLVM) outs() << "\n"; @@ -316,15 +338,24 @@ auto OutputStyle = IsAddr2Line ? DIPrinter::OutputStyle::GNU : DIPrinter::OutputStyle::LLVM; if (const opt::Arg *A = Args.getLastArg(OPT_output_style_EQ)) { - OutputStyle = strcmp(A->getValue(), "GNU") == 0 - ? DIPrinter::OutputStyle::GNU - : DIPrinter::OutputStyle::LLVM; + if (strcmp(A->getValue(), "GNU") == 0) + OutputStyle = DIPrinter::OutputStyle::GNU; + else if (strcmp(A->getValue(), "JSON") == 0) + OutputStyle = DIPrinter::OutputStyle::JSON; + else + OutputStyle = DIPrinter::OutputStyle::LLVM; } LLVMSymbolizer Symbolizer(Opts); - DIPrinter Printer(outs(), Opts.PrintFunctions != FunctionNameKind::None, - Args.hasArg(OPT_pretty_print), SourceContextLines, - Args.hasArg(OPT_verbose), OutputStyle); + std::unique_ptr Printer; + if (OutputStyle == DIPrinter::OutputStyle::JSON) + Printer.reset(new DIPrinterJSON(outs())); + else + Printer.reset( + new DIPrinterGeneric(outs(), OutputStyle, Args.hasArg(OPT_addresses), + Opts.PrintFunctions != FunctionNameKind::None, + Args.hasArg(OPT_pretty_print), SourceContextLines, + Args.hasArg(OPT_verbose))); std::vector InputAddresses = Args.getAllArgValues(OPT_INPUT); if (InputAddresses.empty()) { @@ -337,13 +368,29 @@ llvm::erase_if(StrippedInputString, [](char c) { return c == '\r' || c == '\n'; }); symbolizeInput(Args, AdjustVMA, IsAddr2Line, OutputStyle, - StrippedInputString, Symbolizer, Printer); + StrippedInputString, Symbolizer, *Printer); outs().flush(); } } else { - for (StringRef Address : InputAddresses) + bool ArrayJSON = false; + if (OutputStyle == DIPrinter::OutputStyle::JSON && + InputAddresses.size() > 1) { + ArrayJSON = true; + outs() << "["; + } + bool ArrayDelimJSON = false; + for (StringRef Address : InputAddresses) { + if (ArrayJSON) { + if (ArrayDelimJSON) + outs() << ","; + else + ArrayDelimJSON = true; + } symbolizeInput(Args, AdjustVMA, IsAddr2Line, OutputStyle, Address, - Symbolizer, Printer); + Symbolizer, *Printer); + } + if (ArrayJSON) + outs() << "]\n"; } return 0;