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 @@ -242,6 +242,16 @@ name `_Z3bazv` becomes `baz()`, whilst the non-mangled name `foz` is printed as is). Defaults to true. +.. option:: --dump-process-context, --no-dump-process-context + + Only valid with :option:`--filter-markup`. Emits a JSON representation of the + encountered process contexts instead of symbolizing the markup. A process + context is a map of the process's runtime memory layout obtained from the + contextual markup elements, i.e. `module`, `mmap`, etc. + + Markup may have multiple contexts separated by `reset` elements. The contexts + are returned as a JSON array of objects, even if only one context is present. + .. option:: --dwp Use the specified DWP file at ```` for any CUs that have split DWARF diff --git a/llvm/include/llvm/DebugInfo/Symbolize/MarkupFilter.h b/llvm/include/llvm/DebugInfo/Symbolize/MarkupFilter.h --- a/llvm/include/llvm/DebugInfo/Symbolize/MarkupFilter.h +++ b/llvm/include/llvm/DebugInfo/Symbolize/MarkupFilter.h @@ -18,6 +18,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/DebugInfo/Symbolize/Markup.h" #include "llvm/Object/BuildID.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include @@ -31,8 +32,11 @@ /// text. class MarkupFilter { public: + /// @param DumpProcessContext If true, instead of symbolizing the log, emit a + /// JSON representation of the encountered process contexts. MarkupFilter(raw_ostream &OS, LLVMSymbolizer &Symbolizer, - std::optional ColorsEnabled = std::nullopt); + std::optional ColorsEnabled = std::nullopt, + bool DumpProcessContext = false); /// Filters a line containing symbolizer markup and writes the human-readable /// results to the output stream. @@ -99,6 +103,8 @@ bool tryBackTrace(const MarkupNode &Node); bool tryData(const MarkupNode &Node); + bool tryTrigger(const MarkupNode &Node); + bool trySGR(const MarkupNode &Node); void highlight(); @@ -109,6 +115,8 @@ void printRawElement(const MarkupNode &Element); void printValue(Twine Value); + void dumpProcessContext(); + std::optional parseModule(const MarkupNode &Element) const; std::optional parseMMap(const MarkupNode &Element) const; @@ -136,6 +144,7 @@ StringRef lineEnding() const; raw_ostream &OS; + std::unique_ptr JOS; LLVMSymbolizer &Symbolizer; const bool ColorsEnabled; diff --git a/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp b/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp --- a/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp +++ b/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp @@ -28,6 +28,8 @@ #include "llvm/Support/Error.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Path.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include @@ -36,10 +38,17 @@ using namespace llvm::symbolize; MarkupFilter::MarkupFilter(raw_ostream &OS, LLVMSymbolizer &Symbolizer, - std::optional ColorsEnabled) + std::optional ColorsEnabled, + bool DumpProcessContext) : OS(OS), Symbolizer(Symbolizer), ColorsEnabled( - ColorsEnabled.value_or(WithColor::defaultAutoDetectFunction()(OS))) {} + ColorsEnabled.value_or(WithColor::defaultAutoDetectFunction()(OS))) { + if (DumpProcessContext) { + ColorsEnabled = false; + JOS = std::make_unique(OS, /*Indent=*/2); + JOS->arrayBegin(); + } +} void MarkupFilter::filter(StringRef Line) { this->Line = Line; @@ -70,6 +79,11 @@ filterNode(*Node); endAnyModuleInfoLine(); resetColor(); + if (JOS) { + dumpProcessContext(); + JOS->arrayEnd(); + OS << '\n'; + } Modules.clear(); MMaps.clear(); } @@ -116,7 +130,8 @@ for (const MarkupNode &Node : DeferredNodes) filterNode(Node); beginModuleInfoLine(MMap.Mod); - OS << "; adds"; + if (!JOS) + OS << "; adds"; } MIL->MMaps.push_back(&MMap); return true; @@ -129,13 +144,19 @@ if (!checkNumFields(Node, 0)) return true; + if (JOS) + dumpProcessContext(); + if (!Modules.empty() || !MMaps.empty()) { endAnyModuleInfoLine(); for (const MarkupNode &Node : DeferredNodes) filterNode(Node); - highlight(); - OS << "[[[reset]]]" << lineEnding(); - restoreColor(); + + if (!JOS) { + highlight(); + OS << "[[[reset]]]" << lineEnding(); + restoreColor(); + } Modules.clear(); MMaps.clear(); @@ -164,39 +185,45 @@ for (const MarkupNode &Node : DeferredNodes) filterNode(Node); beginModuleInfoLine(&Module); - OS << "; BuildID="; - printValue(toHex(Module.BuildID, /*LowerCase=*/true)); + if (!JOS) { + OS << "; BuildID="; + printValue(toHex(Module.BuildID, /*LowerCase=*/true)); + } return true; } void MarkupFilter::beginModuleInfoLine(const Module *M) { - highlight(); - OS << "[[[ELF module"; - printValue(formatv(" #{0:x} ", M->ID)); - OS << '"'; - printValue(M->Name); - OS << '"'; + if (!JOS) { + highlight(); + OS << "[[[ELF module"; + printValue(formatv(" #{0:x} ", M->ID)); + OS << '"'; + printValue(M->Name); + OS << '"'; + } MIL = ModuleInfoLine{M}; } void MarkupFilter::endAnyModuleInfoLine() { if (!MIL) return; - llvm::stable_sort(MIL->MMaps, [](const MMap *A, const MMap *B) { - return A->Addr < B->Addr; - }); - for (const MMap *M : MIL->MMaps) { - OS << (M == MIL->MMaps.front() ? ' ' : ','); - OS << '['; - printValue(formatv("{0:x}", M->Addr)); - OS << '-'; - printValue(formatv("{0:x}", M->Addr + M->Size - 1)); - OS << "]("; - printValue(M->Mode); - OS << ')'; + if (!JOS) { + llvm::stable_sort(MIL->MMaps, [](const MMap *A, const MMap *B) { + return A->Addr < B->Addr; + }); + for (const MMap *M : MIL->MMaps) { + OS << (M == MIL->MMaps.front() ? ' ' : ','); + OS << '['; + printValue(formatv("{0:x}", M->Addr)); + OS << '-'; + printValue(formatv("{0:x}", M->Addr + M->Size - 1)); + OS << "]("; + printValue(M->Mode); + OS << ')'; + } + OS << "]]]" << lineEnding(); + restoreColor(); } - OS << "]]]" << lineEnding(); - restoreColor(); MIL.reset(); } @@ -204,6 +231,8 @@ void MarkupFilter::filterNode(const MarkupNode &Node) { if (!checkTag(Node)) return; + if (JOS) + return; if (tryPresentation(Node)) return; if (trySGR(Node)) @@ -400,6 +429,33 @@ return true; } +void MarkupFilter::dumpProcessContext() { + JOS->object([&] { + JOS->attributeArray("modules", [&] { + for (const auto &[_, Module] : Modules) { + JOS->objectBegin(); + JOS->attribute("id", Module->ID); + JOS->attribute("name", Module->Name); + JOS->attribute("type", "elf"); + JOS->attribute("buildID", toHex(Module->BuildID, /*LowerCase=*/true)); + JOS->objectEnd(); + } + }); + JOS->attributeArray("mmaps", [&] { + for (const auto &[_, Map] : MMaps) { + JOS->objectBegin(); + JOS->attribute("address", Map.Addr); + JOS->attribute("size", Map.Size); + JOS->attribute("type", "load"); + JOS->attribute("moduleID", Map.Mod->ID); + JOS->attribute("mode", Map.Mode); + JOS->attribute("moduleRelativeAddress", Map.ModuleRelativeAddr); + JOS->objectEnd(); + } + }); + }); +} + bool MarkupFilter::trySGR(const MarkupNode &Node) { if (Node.Text == "\033[0m") { resetColor(); diff --git a/llvm/test/DebugInfo/symbolize-filter-markup-dump-process-context.test b/llvm/test/DebugInfo/symbolize-filter-markup-dump-process-context.test new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/symbolize-filter-markup-dump-process-context.test @@ -0,0 +1,74 @@ +RUN: split-file %s %t +RUN: llvm-symbolizer --filter-markup --dump-process-context < %t/log > %t.out +RUN: FileCheck %s --input-file=%t.out --match-full-lines \ +RUN: --implicit-check-not {{.}} +RUN: not llvm-symbolizer --dump-process-context 2>&1 | FileCheck --match-full-lines --implicit-check-not {{.}} --check-prefix ERR %s +RUN: not llvm-symbolizer --no-dump-process-context 2>&1 | FileCheck --match-full-lines --implicit-check-not {{.}} --check-prefix ERR %s + +CHECK: [ +CHECK-NEXT: { +CHECK-NEXT: "modules": [ +CHECK-NEXT: { +CHECK-NEXT: "id": 0, +CHECK-NEXT: "name": "b.o", +CHECK-NEXT: "type": "elf", +CHECK-NEXT: "buildID": "ab" +CHECK-NEXT: }, +CHECK-NEXT: { +CHECK-NEXT: "id": 1, +CHECK-NEXT: "name": "a.o", +CHECK-NEXT: "type": "elf", +CHECK-NEXT: "buildID": "cd" +CHECK-NEXT: } +CHECK-NEXT: ], +CHECK-NEXT: "mmaps": [ +CHECK-NEXT: { +CHECK-NEXT: "address": 16, +CHECK-NEXT: "size": 16, +CHECK-NEXT: "type": "load", +CHECK-NEXT: "moduleID": 1, +CHECK-NEXT: "mode": "r", +CHECK-NEXT: "moduleRelativeAddress": 2 +CHECK-NEXT: }, +CHECK-NEXT: { +CHECK-NEXT: "address": 32, +CHECK-NEXT: "size": 48, +CHECK-NEXT: "type": "load", +CHECK-NEXT: "moduleID": 1, +CHECK-NEXT: "mode": "w", +CHECK-NEXT: "moduleRelativeAddress": 3 +CHECK-NEXT: }, +CHECK-NEXT: { +CHECK-NEXT: "address": 80, +CHECK-NEXT: "size": 96, +CHECK-NEXT: "type": "load", +CHECK-NEXT: "moduleID": 0, +CHECK-NEXT: "mode": "rx", +CHECK-NEXT: "moduleRelativeAddress": 4 +CHECK-NEXT: } +CHECK-NEXT: ] +CHECK-NEXT: }, +CHECK-NEXT: { +CHECK-NEXT: "modules": [ +CHECK-NEXT: { +CHECK-NEXT: "id": 0, +CHECK-NEXT: "name": "c.o", +CHECK-NEXT: "type": "elf", +CHECK-NEXT: "buildID": "ef" +CHECK-NEXT: } +CHECK-NEXT: ], +CHECK-NEXT: "mmaps": [] +CHECK-NEXT: } +CHECK-NEXT: ] + +ERR: error: --[no-]dump-process-context can only be used with --filter-markup + +;--- log +{{{module:1:a.o:elf:cd}}} +{{{module:0:b.o:elf:ab}}} +{{{mmap:0x10:0x10:load:1:r:0x2}}} +{{{mmap:0x20:0x30:load:1:w:0x3}}} +{{{mmap:0x50:0x60:load:0:rx:0x4}}} +{{{pc:0x20}}} +{{{reset}}} +{{{module:0:c.o:elf:ef}}} 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 @@ -31,6 +31,7 @@ : Eq<"default-arch", "Default architecture (for multi-arch objects)">, Group; defm demangle : B<"demangle", "Demangle function names", "Don't demangle function names">; +defm dump_process_context : B<"dump-process-context", "Dump process context JSON from markup instead of symbolizing", "Don't dump process contexts">; def filter_markup : Flag<["--"], "filter-markup">, HelpText<"Filter symbolizer markup from stdin.">; def functions : F<"functions", "Print function name for a given address">; def functions_EQ : Joined<["--"], "functions=">, HelpText<"Print function name for a given address">, Values<"none,short,linkage">; 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 @@ -363,7 +363,9 @@ // Symbolize markup from stdin and write the result to stdout. static void filterMarkup(const opt::InputArgList &Args, LLVMSymbolizer &Symbolizer) { - MarkupFilter Filter(outs(), Symbolizer, parseColorArg(Args)); + MarkupFilter Filter(outs(), Symbolizer, parseColorArg(Args), + Args.hasFlag(OPT_dump_process_context, + OPT_no_dump_process_context, false)); std::string InputString; while (std::getline(std::cin, InputString)) { InputString += '\n'; @@ -443,6 +445,13 @@ return 0; } + if (Args.hasArg(OPT_dump_process_context) || + Args.hasArg(OPT_no_dump_process_context)) { + errs() << "error: --[no-]dump-process-context can only be used with " + "--filter-markup\n"; + return EXIT_FAILURE; + } + auto Style = IsAddr2Line ? OutputStyle::GNU : OutputStyle::LLVM; if (const opt::Arg *A = Args.getLastArg(OPT_output_style_EQ)) { if (strcmp(A->getValue(), "GNU") == 0)