1 //===-- lib/DebugInfo/Symbolize/MarkupFilter.cpp -------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
10 /// This file defines the implementation of a filter that replaces symbolizer
11 /// markup with human-readable expressions.
13 /// See https://llvm.org/docs/SymbolizerMarkupFormat.html
15 //===----------------------------------------------------------------------===//
17 #include "llvm/DebugInfo/Symbolize/MarkupFilter.h"
19 #include "llvm/ADT/None.h"
20 #include "llvm/ADT/STLExtras.h"
21 #include "llvm/ADT/StringExtras.h"
22 #include "llvm/ADT/StringSwitch.h"
23 #include "llvm/DebugInfo/Symbolize/Markup.h"
24 #include "llvm/Debuginfod/Debuginfod.h"
25 #include "llvm/Demangle/Demangle.h"
26 #include "llvm/Object/ObjectFile.h"
27 #include "llvm/Support/Error.h"
28 #include "llvm/Support/FormatVariadic.h"
29 #include "llvm/Support/WithColor.h"
30 #include "llvm/Support/raw_ostream.h"
33 using namespace llvm::symbolize;
35 MarkupFilter::MarkupFilter(raw_ostream &OS, Optional<bool> ColorsEnabled)
36 : OS(OS), ColorsEnabled(ColorsEnabled.value_or(
37 WithColor::defaultAutoDetectFunction()(OS))) {}
39 void MarkupFilter::filter(StringRef Line) {
43 Parser.parseLine(Line);
44 SmallVector<MarkupNode> DeferredNodes;
45 // See if the line is a contextual (i.e. contains a contextual element).
46 // In this case, anything after the contextual element is elided, or the whole
47 // line may be elided.
48 while (Optional<MarkupNode> Node = Parser.nextNode()) {
49 // If this was a contextual line, then summarily stop processing.
50 if (tryContextualElement(*Node, DeferredNodes))
52 // This node may yet be part of an elided contextual line.
53 DeferredNodes.push_back(*Node);
56 // This was not a contextual line, so nothing in it should be elided.
57 endAnyModuleInfoLine();
58 for (const MarkupNode &Node : DeferredNodes)
62 void MarkupFilter::finish() {
64 while (Optional<MarkupNode> Node = Parser.nextNode())
66 endAnyModuleInfoLine();
72 // See if the given node is a contextual element and handle it if so. This may
73 // either output or defer the element; in the former case, it will first emit
76 // Returns true if the given element was a contextual element. In this case,
77 // DeferredNodes should be considered handled and should not be emitted. The
78 // rest of the containing line must also be ignored in case the element was
79 // deferred to a following line.
80 bool MarkupFilter::tryContextualElement(
81 const MarkupNode &Node, const SmallVector<MarkupNode> &DeferredNodes) {
82 if (tryMMap(Node, DeferredNodes))
84 if (tryReset(Node, DeferredNodes))
86 return tryModule(Node, DeferredNodes);
89 bool MarkupFilter::tryMMap(const MarkupNode &Node,
90 const SmallVector<MarkupNode> &DeferredNodes) {
91 if (Node.Tag != "mmap")
93 Optional<MMap> ParsedMMap = parseMMap(Node);
97 if (const MMap *M = overlappingMMap(*ParsedMMap)) {
98 WithColor::error(errs())
99 << formatv("overlapping mmap: #{0:x} [{1:x},{2:x})\n", M->Mod->ID,
100 M->Addr, M->Addr + M->Size);
101 reportLocation(Node.Fields[0].begin());
105 auto Res = MMaps.emplace(ParsedMMap->Addr, std::move(*ParsedMMap));
106 assert(Res.second && "Overlap check should ensure emplace succeeds.");
107 MMap &MMap = Res.first->second;
109 if (!MIL || MIL->Mod != MMap.Mod) {
110 endAnyModuleInfoLine();
111 for (const MarkupNode &Node : DeferredNodes)
113 beginModuleInfoLine(MMap.Mod);
116 MIL->MMaps.push_back(&MMap);
120 bool MarkupFilter::tryReset(const MarkupNode &Node,
121 const SmallVector<MarkupNode> &DeferredNodes) {
122 if (Node.Tag != "reset")
124 if (!checkNumFields(Node, 0))
127 if (!Modules.empty() || !MMaps.empty()) {
128 endAnyModuleInfoLine();
129 for (const MarkupNode &Node : DeferredNodes)
132 OS << "[[[reset]]]" << lineEnding();
141 bool MarkupFilter::tryModule(const MarkupNode &Node,
142 const SmallVector<MarkupNode> &DeferredNodes) {
143 if (Node.Tag != "module")
145 Optional<Module> ParsedModule = parseModule(Node);
149 auto Res = Modules.try_emplace(
150 ParsedModule->ID, std::make_unique<Module>(std::move(*ParsedModule)));
152 WithColor::error(errs()) << "duplicate module ID\n";
153 reportLocation(Node.Fields[0].begin());
156 Module &Module = *Res.first->second;
158 endAnyModuleInfoLine();
159 for (const MarkupNode &Node : DeferredNodes)
161 beginModuleInfoLine(&Module);
164 OS << toHex(Module.BuildID, /*LowerCase=*/true);
169 void MarkupFilter::beginModuleInfoLine(const Module *M) {
171 OS << "[[[ELF module";
173 OS << formatv(" #{0:x} \"{1}\"", M->ID, M->Name);
175 MIL = ModuleInfoLine{M};
178 void MarkupFilter::endAnyModuleInfoLine() {
181 llvm::stable_sort(MIL->MMaps, [](const MMap *A, const MMap *B) {
182 return A->Addr < B->Addr;
184 for (const MMap *M : MIL->MMaps) {
185 OS << (M == MIL->MMaps.front() ? ' ' : '-');
187 OS << formatv("{0:x}", M->Addr);
195 OS << "]]]" << lineEnding();
200 // Handle a node that is known not to be a contextual element.
201 void MarkupFilter::filterNode(const MarkupNode &Node) {
204 if (tryPresentation(Node))
212 bool MarkupFilter::tryPresentation(const MarkupNode &Node) {
213 return trySymbol(Node);
216 bool MarkupFilter::trySymbol(const MarkupNode &Node) {
217 if (Node.Tag != "symbol")
219 if (!checkNumFields(Node, 1))
223 OS << llvm::demangle(Node.Fields.front().str());
228 bool MarkupFilter::trySGR(const MarkupNode &Node) {
229 if (Node.Text == "\033[0m") {
233 if (Node.Text == "\033[1m") {
236 OS.changeColor(raw_ostream::Colors::SAVEDCOLOR, Bold);
239 auto SGRColor = StringSwitch<Optional<raw_ostream::Colors>>(Node.Text)
240 .Case("\033[30m", raw_ostream::Colors::BLACK)
241 .Case("\033[31m", raw_ostream::Colors::RED)
242 .Case("\033[32m", raw_ostream::Colors::GREEN)
243 .Case("\033[33m", raw_ostream::Colors::YELLOW)
244 .Case("\033[34m", raw_ostream::Colors::BLUE)
245 .Case("\033[35m", raw_ostream::Colors::MAGENTA)
246 .Case("\033[36m", raw_ostream::Colors::CYAN)
247 .Case("\033[37m", raw_ostream::Colors::WHITE)
248 .Default(llvm::None);
252 OS.changeColor(*Color);
259 // Begin highlighting text by picking a different color than the current color
261 void MarkupFilter::highlight() {
264 OS.changeColor(Color == raw_ostream::Colors::BLUE ? raw_ostream::Colors::CYAN
265 : raw_ostream::Colors::BLUE,
269 // Begin highlighting a field within a highlighted markup string.
270 void MarkupFilter::highlightValue() {
273 OS.changeColor(raw_ostream::Colors::GREEN, Bold);
276 // Set the output stream's color to the current color and bold state of the SGR
278 void MarkupFilter::restoreColor() {
282 OS.changeColor(*Color, Bold);
286 OS.changeColor(raw_ostream::Colors::SAVEDCOLOR, Bold);
290 // Set the SGR and output stream's color and bold states back to the default.
291 void MarkupFilter::resetColor() {
300 // This macro helps reduce the amount of indirection done through Optional
301 // below, since the usual case upon returning a None Optional is to return None.
302 #define ASSIGN_OR_RETURN_NONE(TYPE, NAME, EXPR) \
303 auto NAME##Opt = (EXPR); \
306 TYPE NAME = std::move(*NAME##Opt)
308 Optional<MarkupFilter::Module>
309 MarkupFilter::parseModule(const MarkupNode &Element) const {
310 if (!checkNumFieldsAtLeast(Element, 3))
312 ASSIGN_OR_RETURN_NONE(uint64_t, ID, parseModuleID(Element.Fields[0]));
313 StringRef Name = Element.Fields[1];
314 StringRef Type = Element.Fields[2];
316 WithColor::error() << "unknown module type\n";
317 reportLocation(Type.begin());
320 if (!checkNumFields(Element, 4))
322 ASSIGN_OR_RETURN_NONE(SmallVector<uint8_t>, BuildID,
323 parseBuildID(Element.Fields[3]));
324 return Module{ID, Name.str(), std::move(BuildID)};
327 Optional<MarkupFilter::MMap>
328 MarkupFilter::parseMMap(const MarkupNode &Element) const {
329 if (!checkNumFieldsAtLeast(Element, 3))
331 ASSIGN_OR_RETURN_NONE(uint64_t, Addr, parseAddr(Element.Fields[0]));
332 ASSIGN_OR_RETURN_NONE(uint64_t, Size, parseSize(Element.Fields[1]));
333 StringRef Type = Element.Fields[2];
334 if (Type != "load") {
335 WithColor::error() << "unknown mmap type\n";
336 reportLocation(Type.begin());
339 if (!checkNumFields(Element, 6))
341 ASSIGN_OR_RETURN_NONE(uint64_t, ID, parseModuleID(Element.Fields[3]));
342 ASSIGN_OR_RETURN_NONE(std::string, Mode, parseMode(Element.Fields[4]));
343 auto It = Modules.find(ID);
344 if (It == Modules.end()) {
345 WithColor::error() << "unknown module ID\n";
346 reportLocation(Element.Fields[3].begin());
349 ASSIGN_OR_RETURN_NONE(uint64_t, ModuleRelativeAddr,
350 parseAddr(Element.Fields[5]));
351 return MMap{Addr, Size, It->second.get(), std::move(Mode),
355 // Parse an address (%p in the spec).
356 Optional<uint64_t> MarkupFilter::parseAddr(StringRef Str) const {
358 reportTypeError(Str, "address");
361 if (all_of(Str, [](char C) { return C == '0'; }))
363 if (!Str.startswith("0x")) {
364 reportTypeError(Str, "address");
368 if (Str.drop_front(2).getAsInteger(16, Addr)) {
369 reportTypeError(Str, "address");
375 // Parse a module ID (%i in the spec).
376 Optional<uint64_t> MarkupFilter::parseModuleID(StringRef Str) const {
378 if (Str.getAsInteger(0, ID)) {
379 reportTypeError(Str, "module ID");
385 // Parse a size (%i in the spec).
386 Optional<uint64_t> MarkupFilter::parseSize(StringRef Str) const {
388 if (Str.getAsInteger(0, ID)) {
389 reportTypeError(Str, "size");
395 // Parse a build ID (%x in the spec).
396 Optional<SmallVector<uint8_t>> MarkupFilter::parseBuildID(StringRef Str) const {
398 if (Str.empty() || Str.size() % 2 || !tryGetFromHex(Str, Bytes)) {
399 reportTypeError(Str, "build ID");
402 ArrayRef<uint8_t> BuildID(reinterpret_cast<const uint8_t *>(Bytes.data()),
404 return SmallVector<uint8_t>(BuildID.begin(), BuildID.end());
407 // Parses the mode string for an mmap element.
408 Optional<std::string> MarkupFilter::parseMode(StringRef Str) const {
410 reportTypeError(Str, "mode");
414 // Pop off each of r/R, w/W, and x/X from the front, in that order.
415 StringRef Remainder = Str;
416 if (!Remainder.empty() && tolower(Remainder.front()) == 'r')
417 Remainder = Remainder.drop_front();
418 if (!Remainder.empty() && tolower(Remainder.front()) == 'w')
419 Remainder = Remainder.drop_front();
420 if (!Remainder.empty() && tolower(Remainder.front()) == 'x')
421 Remainder = Remainder.drop_front();
423 // If anything remains, then the string wasn't a mode.
424 if (!Remainder.empty()) {
425 reportTypeError(Str, "mode");
429 // Normalize the mode.
433 bool MarkupFilter::checkTag(const MarkupNode &Node) const {
434 if (any_of(Node.Tag, [](char C) { return C < 'a' || C > 'z'; })) {
435 WithColor::error(errs()) << "tags must be all lowercase characters\n";
436 reportLocation(Node.Tag.begin());
442 bool MarkupFilter::checkNumFields(const MarkupNode &Element,
444 if (Element.Fields.size() != Size) {
445 WithColor::error(errs()) << "expected " << Size << " fields; found "
446 << Element.Fields.size() << "\n";
447 reportLocation(Element.Tag.end());
453 bool MarkupFilter::checkNumFieldsAtLeast(const MarkupNode &Element,
455 if (Element.Fields.size() < Size) {
456 WithColor::error(errs())
457 << "expected at least " << Size << " fields; found "
458 << Element.Fields.size() << "\n";
459 reportLocation(Element.Tag.end());
465 void MarkupFilter::reportTypeError(StringRef Str, StringRef TypeName) const {
466 WithColor::error(errs()) << "expected " << TypeName << "; found '" << Str
468 reportLocation(Str.begin());
471 // Prints two lines that point out the given location in the current Line using
472 // a caret. The iterator must be within the bounds of the most recent line
473 // passed to beginLine().
474 void MarkupFilter::reportLocation(StringRef::iterator Loc) const {
476 WithColor(errs().indent(Loc - Line.begin()), HighlightColor::String) << '^';
480 // Checks for an existing mmap that overlaps the given one and returns a
481 // pointer to one of them.
482 const MarkupFilter::MMap *MarkupFilter::overlappingMMap(const MMap &Map) const {
483 // If the given map contains the start of another mmap, they overlap.
484 auto I = MMaps.upper_bound(Map.Addr);
485 if (I != MMaps.end() && Map.contains(I->second.Addr))
488 // If no element starts inside the given mmap, the only possible overlap would
489 // be if the preceding mmap contains the start point of the given mmap.
490 if (I != MMaps.begin()) {
492 if (I->second.contains(Map.Addr))
498 StringRef MarkupFilter::lineEnding() const {
499 return Line.endswith("\r\n") ? "\r\n" : "\n";
502 bool MarkupFilter::MMap::contains(uint64_t Addr) const {
503 return this->Addr <= Addr && Addr < this->Addr + Size;