1 | //===-- lib/DebugInfo/Symbolize/MarkupFilter.cpp -------------------------===// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | /// |
9 | /// \file |
10 | /// This file defines the implementation of a filter that replaces symbolizer |
11 | /// markup with human-readable expressions. |
12 | /// |
13 | /// See https://llvm.org/docs/SymbolizerMarkupFormat.html |
14 | /// |
15 | //===----------------------------------------------------------------------===// |
16 | |
17 | #include "llvm/DebugInfo/Symbolize/MarkupFilter.h" |
18 | |
19 | #include "llvm/ADT/STLExtras.h" |
20 | #include "llvm/ADT/StringExtras.h" |
21 | #include "llvm/ADT/StringSwitch.h" |
22 | #include "llvm/DebugInfo/DIContext.h" |
23 | #include "llvm/DebugInfo/Symbolize/Markup.h" |
24 | #include "llvm/DebugInfo/Symbolize/Symbolize.h" |
25 | #include "llvm/Debuginfod/Debuginfod.h" |
26 | #include "llvm/Demangle/Demangle.h" |
27 | #include "llvm/Object/ObjectFile.h" |
28 | #include "llvm/Support/Error.h" |
29 | #include "llvm/Support/Format.h" |
30 | #include "llvm/Support/FormatVariadic.h" |
31 | #include "llvm/Support/WithColor.h" |
32 | #include "llvm/Support/raw_ostream.h" |
33 | #include <optional> |
34 | |
35 | using namespace llvm; |
36 | using namespace llvm::symbolize; |
37 | |
38 | MarkupFilter::MarkupFilter(raw_ostream &OS, LLVMSymbolizer &Symbolizer, |
39 | std::optional<bool> ColorsEnabled) |
40 | : OS(OS), Symbolizer(Symbolizer), |
41 | ColorsEnabled( |
42 | ColorsEnabled.value_or(u: WithColor::defaultAutoDetectFunction()(OS))) {} |
43 | |
44 | void MarkupFilter::filter(std::string &&InputLine) { |
45 | Line = std::move(InputLine); |
46 | resetColor(); |
47 | |
48 | Parser.parseLine(Line); |
49 | SmallVector<MarkupNode> DeferredNodes; |
50 | // See if the line is a contextual (i.e. contains a contextual element). |
51 | // In this case, anything after the contextual element is elided, or the whole |
52 | // line may be elided. |
53 | while (std::optional<MarkupNode> Node = Parser.nextNode()) { |
54 | // If this was a contextual line, then summarily stop processing. |
55 | if (tryContextualElement(Node: *Node, DeferredNodes)) |
56 | return; |
57 | // This node may yet be part of an elided contextual line. |
58 | DeferredNodes.push_back(Elt: *Node); |
59 | } |
60 | |
61 | // This was not a contextual line, so nothing in it should be elided. |
62 | endAnyModuleInfoLine(); |
63 | for (const MarkupNode &Node : DeferredNodes) |
64 | filterNode(Node); |
65 | } |
66 | |
67 | void MarkupFilter::finish() { |
68 | Parser.flush(); |
69 | while (std::optional<MarkupNode> Node = Parser.nextNode()) |
70 | filterNode(Node: *Node); |
71 | endAnyModuleInfoLine(); |
72 | resetColor(); |
73 | Modules.clear(); |
74 | MMaps.clear(); |
75 | } |
76 | |
77 | // See if the given node is a contextual element and handle it if so. This may |
78 | // either output or defer the element; in the former case, it will first emit |
79 | // any DeferredNodes. |
80 | // |
81 | // Returns true if the given element was a contextual element. In this case, |
82 | // DeferredNodes should be considered handled and should not be emitted. The |
83 | // rest of the containing line must also be ignored in case the element was |
84 | // deferred to a following line. |
85 | bool MarkupFilter::tryContextualElement( |
86 | const MarkupNode &Node, const SmallVector<MarkupNode> &DeferredNodes) { |
87 | if (tryMMap(Element: Node, DeferredNodes)) |
88 | return true; |
89 | if (tryReset(Element: Node, DeferredNodes)) |
90 | return true; |
91 | return tryModule(Element: Node, DeferredNodes); |
92 | } |
93 | |
94 | bool MarkupFilter::tryMMap(const MarkupNode &Node, |
95 | const SmallVector<MarkupNode> &DeferredNodes) { |
96 | if (Node.Tag != "mmap" ) |
97 | return false; |
98 | std::optional<MMap> ParsedMMap = parseMMap(Element: Node); |
99 | if (!ParsedMMap) |
100 | return true; |
101 | |
102 | if (const MMap *M = getOverlappingMMap(Map: *ParsedMMap)) { |
103 | WithColor::error(OS&: errs()) |
104 | << formatv(Fmt: "overlapping mmap: #{0:x} [{1:x}-{2:x}]\n" , Vals: M->Mod->ID, |
105 | Vals: M->Addr, Vals: M->Addr + M->Size - 1); |
106 | reportLocation(Loc: Node.Fields[0].begin()); |
107 | return true; |
108 | } |
109 | |
110 | auto Res = MMaps.emplace(args&: ParsedMMap->Addr, args: std::move(*ParsedMMap)); |
111 | assert(Res.second && "Overlap check should ensure emplace succeeds." ); |
112 | MMap &MMap = Res.first->second; |
113 | |
114 | if (!MIL || MIL->Mod != MMap.Mod) { |
115 | endAnyModuleInfoLine(); |
116 | for (const MarkupNode &Node : DeferredNodes) |
117 | filterNode(Node); |
118 | beginModuleInfoLine(M: MMap.Mod); |
119 | OS << "; adds" ; |
120 | } |
121 | MIL->MMaps.push_back(Elt: &MMap); |
122 | return true; |
123 | } |
124 | |
125 | bool MarkupFilter::tryReset(const MarkupNode &Node, |
126 | const SmallVector<MarkupNode> &DeferredNodes) { |
127 | if (Node.Tag != "reset" ) |
128 | return false; |
129 | if (!checkNumFields(Element: Node, Size: 0)) |
130 | return true; |
131 | |
132 | if (!Modules.empty() || !MMaps.empty()) { |
133 | endAnyModuleInfoLine(); |
134 | for (const MarkupNode &Node : DeferredNodes) |
135 | filterNode(Node); |
136 | printRawElement(Element: Node); |
137 | OS << lineEnding(); |
138 | |
139 | Modules.clear(); |
140 | MMaps.clear(); |
141 | } |
142 | return true; |
143 | } |
144 | |
145 | bool MarkupFilter::tryModule(const MarkupNode &Node, |
146 | const SmallVector<MarkupNode> &DeferredNodes) { |
147 | if (Node.Tag != "module" ) |
148 | return false; |
149 | std::optional<Module> ParsedModule = parseModule(Element: Node); |
150 | if (!ParsedModule) |
151 | return true; |
152 | |
153 | auto Res = Modules.try_emplace( |
154 | Key: ParsedModule->ID, Args: std::make_unique<Module>(args: std::move(*ParsedModule))); |
155 | if (!Res.second) { |
156 | WithColor::error(OS&: errs()) << "duplicate module ID\n" ; |
157 | reportLocation(Loc: Node.Fields[0].begin()); |
158 | return true; |
159 | } |
160 | Module &Module = *Res.first->second; |
161 | |
162 | endAnyModuleInfoLine(); |
163 | for (const MarkupNode &Node : DeferredNodes) |
164 | filterNode(Node); |
165 | beginModuleInfoLine(M: &Module); |
166 | OS << "; BuildID=" ; |
167 | printValue(Value: toHex(Input: Module.BuildID, /*LowerCase=*/true)); |
168 | return true; |
169 | } |
170 | |
171 | void MarkupFilter::beginModuleInfoLine(const Module *M) { |
172 | highlight(); |
173 | OS << "[[[ELF module" ; |
174 | printValue(Value: formatv(Fmt: " #{0:x} " , Vals: M->ID)); |
175 | OS << '"'; |
176 | printValue(Value: M->Name); |
177 | OS << '"'; |
178 | MIL = ModuleInfoLine{.Mod: M}; |
179 | } |
180 | |
181 | void MarkupFilter::endAnyModuleInfoLine() { |
182 | if (!MIL) |
183 | return; |
184 | llvm::stable_sort(Range&: MIL->MMaps, C: [](const MMap *A, const MMap *B) { |
185 | return A->Addr < B->Addr; |
186 | }); |
187 | for (const MMap *M : MIL->MMaps) { |
188 | OS << (M == MIL->MMaps.front() ? ' ' : ','); |
189 | OS << '['; |
190 | printValue(Value: formatv(Fmt: "{0:x}" , Vals: M->Addr)); |
191 | OS << '-'; |
192 | printValue(Value: formatv(Fmt: "{0:x}" , Vals: M->Addr + M->Size - 1)); |
193 | OS << "](" ; |
194 | printValue(Value: M->Mode); |
195 | OS << ')'; |
196 | } |
197 | OS << "]]]" << lineEnding(); |
198 | restoreColor(); |
199 | MIL.reset(); |
200 | } |
201 | |
202 | // Handle a node that is known not to be a contextual element. |
203 | void MarkupFilter::filterNode(const MarkupNode &Node) { |
204 | if (!checkTag(Node)) |
205 | return; |
206 | if (tryPresentation(Node)) |
207 | return; |
208 | if (trySGR(Node)) |
209 | return; |
210 | |
211 | OS << Node.Text; |
212 | } |
213 | |
214 | bool MarkupFilter::tryPresentation(const MarkupNode &Node) { |
215 | if (trySymbol(Node)) |
216 | return true; |
217 | if (tryPC(Node)) |
218 | return true; |
219 | if (tryBackTrace(Node)) |
220 | return true; |
221 | return tryData(Node); |
222 | } |
223 | |
224 | bool MarkupFilter::trySymbol(const MarkupNode &Node) { |
225 | if (Node.Tag != "symbol" ) |
226 | return false; |
227 | if (!checkNumFields(Element: Node, Size: 1)) |
228 | return true; |
229 | |
230 | highlight(); |
231 | OS << llvm::demangle(MangledName: Node.Fields.front().str()); |
232 | restoreColor(); |
233 | return true; |
234 | } |
235 | |
236 | bool MarkupFilter::tryPC(const MarkupNode &Node) { |
237 | if (Node.Tag != "pc" ) |
238 | return false; |
239 | if (!checkNumFieldsAtLeast(Element: Node, Size: 1)) |
240 | return true; |
241 | warnNumFieldsAtMost(Element: Node, Size: 2); |
242 | |
243 | std::optional<uint64_t> Addr = parseAddr(Str: Node.Fields[0]); |
244 | if (!Addr) |
245 | return true; |
246 | |
247 | // PC addresses that aren't part of a backtrace are assumed to be precise code |
248 | // locations. |
249 | PCType Type = PCType::PreciseCode; |
250 | if (Node.Fields.size() == 2) { |
251 | std::optional<PCType> ParsedType = parsePCType(Str: Node.Fields[1]); |
252 | if (!ParsedType) |
253 | return true; |
254 | Type = *ParsedType; |
255 | } |
256 | *Addr = adjustAddr(Addr: *Addr, Type); |
257 | |
258 | const MMap *MMap = getContainingMMap(Addr: *Addr); |
259 | if (!MMap) { |
260 | WithColor::error() << "no mmap covers address\n" ; |
261 | reportLocation(Loc: Node.Fields[0].begin()); |
262 | printRawElement(Element: Node); |
263 | return true; |
264 | } |
265 | |
266 | Expected<DILineInfo> LI = Symbolizer.symbolizeCode( |
267 | BuildID: MMap->Mod->BuildID, ModuleOffset: {.Address: MMap->getModuleRelativeAddr(Addr: *Addr)}); |
268 | if (!LI) { |
269 | WithColor::defaultErrorHandler(Err: LI.takeError()); |
270 | printRawElement(Element: Node); |
271 | return true; |
272 | } |
273 | if (!*LI) { |
274 | printRawElement(Element: Node); |
275 | return true; |
276 | } |
277 | |
278 | highlight(); |
279 | printValue(Value: LI->FunctionName); |
280 | OS << '['; |
281 | printValue(Value: LI->FileName); |
282 | OS << ':'; |
283 | printValue(Value: Twine(LI->Line)); |
284 | OS << ']'; |
285 | restoreColor(); |
286 | return true; |
287 | } |
288 | |
289 | bool MarkupFilter::tryBackTrace(const MarkupNode &Node) { |
290 | if (Node.Tag != "bt" ) |
291 | return false; |
292 | if (!checkNumFieldsAtLeast(Element: Node, Size: 2)) |
293 | return true; |
294 | warnNumFieldsAtMost(Element: Node, Size: 3); |
295 | |
296 | std::optional<uint64_t> = parseFrameNumber(Str: Node.Fields[0]); |
297 | if (!FrameNumber) |
298 | return true; |
299 | |
300 | std::optional<uint64_t> Addr = parseAddr(Str: Node.Fields[1]); |
301 | if (!Addr) |
302 | return true; |
303 | |
304 | // Backtrace addresses are assumed to be return addresses by default. |
305 | PCType Type = PCType::ReturnAddress; |
306 | if (Node.Fields.size() == 3) { |
307 | std::optional<PCType> ParsedType = parsePCType(Str: Node.Fields[2]); |
308 | if (!ParsedType) |
309 | return true; |
310 | Type = *ParsedType; |
311 | } |
312 | *Addr = adjustAddr(Addr: *Addr, Type); |
313 | |
314 | const MMap *MMap = getContainingMMap(Addr: *Addr); |
315 | if (!MMap) { |
316 | WithColor::error() << "no mmap covers address\n" ; |
317 | reportLocation(Loc: Node.Fields[0].begin()); |
318 | printRawElement(Element: Node); |
319 | return true; |
320 | } |
321 | uint64_t MRA = MMap->getModuleRelativeAddr(Addr: *Addr); |
322 | |
323 | Expected<DIInliningInfo> II = |
324 | Symbolizer.symbolizeInlinedCode(BuildID: MMap->Mod->BuildID, ModuleOffset: {.Address: MRA}); |
325 | if (!II) { |
326 | WithColor::defaultErrorHandler(Err: II.takeError()); |
327 | printRawElement(Element: Node); |
328 | return true; |
329 | } |
330 | |
331 | highlight(); |
332 | for (unsigned I = 0, E = II->getNumberOfFrames(); I != E; ++I) { |
333 | auto = formatv(Fmt: "{0, +6}" , Vals: formatv(Fmt: "#{0}" , Vals&: FrameNumber)).sstr<16>(); |
334 | // Don't highlight the # sign as a value. |
335 | size_t NumberIdx = Header.find(Str: "#" ) + 1; |
336 | OS << Header.substr(Start: 0, N: NumberIdx); |
337 | printValue(Value: Header.substr(Start: NumberIdx)); |
338 | if (I == E - 1) { |
339 | OS << " " ; |
340 | } else { |
341 | OS << '.'; |
342 | printValue(Value: formatv(Fmt: "{0, -2}" , Vals: I + 1)); |
343 | } |
344 | printValue(Value: formatv(Fmt: " {0:x16} " , Vals&: *Addr)); |
345 | |
346 | DILineInfo LI = II->getFrame(Index: I); |
347 | if (LI) { |
348 | printValue(Value: LI.FunctionName); |
349 | OS << ' '; |
350 | printValue(Value: LI.FileName); |
351 | OS << ':'; |
352 | printValue(Value: Twine(LI.Line)); |
353 | OS << ':'; |
354 | printValue(Value: Twine(LI.Column)); |
355 | OS << ' '; |
356 | } |
357 | OS << '('; |
358 | printValue(Value: MMap->Mod->Name); |
359 | OS << "+" ; |
360 | printValue(Value: formatv(Fmt: "{0:x}" , Vals&: MRA)); |
361 | OS << ')'; |
362 | if (I != E - 1) |
363 | OS << lineEnding(); |
364 | } |
365 | restoreColor(); |
366 | return true; |
367 | } |
368 | |
369 | bool MarkupFilter::tryData(const MarkupNode &Node) { |
370 | if (Node.Tag != "data" ) |
371 | return false; |
372 | if (!checkNumFields(Element: Node, Size: 1)) |
373 | return true; |
374 | std::optional<uint64_t> Addr = parseAddr(Str: Node.Fields[0]); |
375 | if (!Addr) |
376 | return true; |
377 | |
378 | const MMap *MMap = getContainingMMap(Addr: *Addr); |
379 | if (!MMap) { |
380 | WithColor::error() << "no mmap covers address\n" ; |
381 | reportLocation(Loc: Node.Fields[0].begin()); |
382 | printRawElement(Element: Node); |
383 | return true; |
384 | } |
385 | |
386 | Expected<DIGlobal> Symbol = Symbolizer.symbolizeData( |
387 | BuildID: MMap->Mod->BuildID, ModuleOffset: {.Address: MMap->getModuleRelativeAddr(Addr: *Addr)}); |
388 | if (!Symbol) { |
389 | WithColor::defaultErrorHandler(Err: Symbol.takeError()); |
390 | printRawElement(Element: Node); |
391 | return true; |
392 | } |
393 | |
394 | highlight(); |
395 | OS << Symbol->Name; |
396 | restoreColor(); |
397 | return true; |
398 | } |
399 | |
400 | bool MarkupFilter::trySGR(const MarkupNode &Node) { |
401 | if (Node.Text == "\033[0m" ) { |
402 | resetColor(); |
403 | return true; |
404 | } |
405 | if (Node.Text == "\033[1m" ) { |
406 | Bold = true; |
407 | if (ColorsEnabled) |
408 | OS.changeColor(Color: raw_ostream::Colors::SAVEDCOLOR, Bold); |
409 | return true; |
410 | } |
411 | auto SGRColor = StringSwitch<std::optional<raw_ostream::Colors>>(Node.Text) |
412 | .Case(S: "\033[30m" , Value: raw_ostream::Colors::BLACK) |
413 | .Case(S: "\033[31m" , Value: raw_ostream::Colors::RED) |
414 | .Case(S: "\033[32m" , Value: raw_ostream::Colors::GREEN) |
415 | .Case(S: "\033[33m" , Value: raw_ostream::Colors::YELLOW) |
416 | .Case(S: "\033[34m" , Value: raw_ostream::Colors::BLUE) |
417 | .Case(S: "\033[35m" , Value: raw_ostream::Colors::MAGENTA) |
418 | .Case(S: "\033[36m" , Value: raw_ostream::Colors::CYAN) |
419 | .Case(S: "\033[37m" , Value: raw_ostream::Colors::WHITE) |
420 | .Default(Value: std::nullopt); |
421 | if (SGRColor) { |
422 | Color = *SGRColor; |
423 | if (ColorsEnabled) |
424 | OS.changeColor(Color: *Color); |
425 | return true; |
426 | } |
427 | |
428 | return false; |
429 | } |
430 | |
431 | // Begin highlighting text by picking a different color than the current color |
432 | // state. |
433 | void MarkupFilter::highlight() { |
434 | if (!ColorsEnabled) |
435 | return; |
436 | OS.changeColor(Color: Color == raw_ostream::Colors::BLUE ? raw_ostream::Colors::CYAN |
437 | : raw_ostream::Colors::BLUE, |
438 | Bold); |
439 | } |
440 | |
441 | // Begin highlighting a field within a highlighted markup string. |
442 | void MarkupFilter::highlightValue() { |
443 | if (!ColorsEnabled) |
444 | return; |
445 | OS.changeColor(Color: raw_ostream::Colors::GREEN, Bold); |
446 | } |
447 | |
448 | // Set the output stream's color to the current color and bold state of the SGR |
449 | // abstract machine. |
450 | void MarkupFilter::restoreColor() { |
451 | if (!ColorsEnabled) |
452 | return; |
453 | if (Color) { |
454 | OS.changeColor(Color: *Color, Bold); |
455 | } else { |
456 | OS.resetColor(); |
457 | if (Bold) |
458 | OS.changeColor(Color: raw_ostream::Colors::SAVEDCOLOR, Bold); |
459 | } |
460 | } |
461 | |
462 | // Set the SGR and output stream's color and bold states back to the default. |
463 | void MarkupFilter::resetColor() { |
464 | if (!Color && !Bold) |
465 | return; |
466 | Color.reset(); |
467 | Bold = false; |
468 | if (ColorsEnabled) |
469 | OS.resetColor(); |
470 | } |
471 | |
472 | void MarkupFilter::printRawElement(const MarkupNode &Element) { |
473 | highlight(); |
474 | OS << "[[[" ; |
475 | printValue(Value: Element.Tag); |
476 | for (StringRef Field : Element.Fields) { |
477 | OS << ':'; |
478 | printValue(Value: Field); |
479 | } |
480 | OS << "]]]" ; |
481 | restoreColor(); |
482 | } |
483 | |
484 | void MarkupFilter::printValue(Twine Value) { |
485 | highlightValue(); |
486 | OS << Value; |
487 | highlight(); |
488 | } |
489 | |
490 | // This macro helps reduce the amount of indirection done through Optional |
491 | // below, since the usual case upon returning a std::nullopt Optional is to |
492 | // return std::nullopt. |
493 | #define ASSIGN_OR_RETURN_NONE(TYPE, NAME, EXPR) \ |
494 | auto NAME##Opt = (EXPR); \ |
495 | if (!NAME##Opt) \ |
496 | return std::nullopt; \ |
497 | TYPE NAME = std::move(*NAME##Opt) |
498 | |
499 | std::optional<MarkupFilter::Module> |
500 | MarkupFilter::parseModule(const MarkupNode &Element) const { |
501 | if (!checkNumFieldsAtLeast(Element, Size: 3)) |
502 | return std::nullopt; |
503 | ASSIGN_OR_RETURN_NONE(uint64_t, ID, parseModuleID(Element.Fields[0])); |
504 | StringRef Name = Element.Fields[1]; |
505 | StringRef Type = Element.Fields[2]; |
506 | if (Type != "elf" ) { |
507 | WithColor::error() << "unknown module type\n" ; |
508 | reportLocation(Loc: Type.begin()); |
509 | return std::nullopt; |
510 | } |
511 | if (!checkNumFields(Element, Size: 4)) |
512 | return std::nullopt; |
513 | SmallVector<uint8_t> BuildID = parseBuildID(Str: Element.Fields[3]); |
514 | if (BuildID.empty()) |
515 | return std::nullopt; |
516 | return Module{.ID: ID, .Name: Name.str(), .BuildID: std::move(BuildID)}; |
517 | } |
518 | |
519 | std::optional<MarkupFilter::MMap> |
520 | MarkupFilter::parseMMap(const MarkupNode &Element) const { |
521 | if (!checkNumFieldsAtLeast(Element, Size: 3)) |
522 | return std::nullopt; |
523 | ASSIGN_OR_RETURN_NONE(uint64_t, Addr, parseAddr(Element.Fields[0])); |
524 | ASSIGN_OR_RETURN_NONE(uint64_t, Size, parseSize(Element.Fields[1])); |
525 | StringRef Type = Element.Fields[2]; |
526 | if (Type != "load" ) { |
527 | WithColor::error() << "unknown mmap type\n" ; |
528 | reportLocation(Loc: Type.begin()); |
529 | return std::nullopt; |
530 | } |
531 | if (!checkNumFields(Element, Size: 6)) |
532 | return std::nullopt; |
533 | ASSIGN_OR_RETURN_NONE(uint64_t, ID, parseModuleID(Element.Fields[3])); |
534 | ASSIGN_OR_RETURN_NONE(std::string, Mode, parseMode(Element.Fields[4])); |
535 | auto It = Modules.find(Val: ID); |
536 | if (It == Modules.end()) { |
537 | WithColor::error() << "unknown module ID\n" ; |
538 | reportLocation(Loc: Element.Fields[3].begin()); |
539 | return std::nullopt; |
540 | } |
541 | ASSIGN_OR_RETURN_NONE(uint64_t, ModuleRelativeAddr, |
542 | parseAddr(Element.Fields[5])); |
543 | return MMap{.Addr: Addr, .Size: Size, .Mod: It->second.get(), .Mode: std::move(Mode), |
544 | .ModuleRelativeAddr: ModuleRelativeAddr}; |
545 | } |
546 | |
547 | // Parse an address (%p in the spec). |
548 | std::optional<uint64_t> MarkupFilter::parseAddr(StringRef Str) const { |
549 | if (Str.empty()) { |
550 | reportTypeError(Str, TypeName: "address" ); |
551 | return std::nullopt; |
552 | } |
553 | if (all_of(Range&: Str, P: [](char C) { return C == '0'; })) |
554 | return 0; |
555 | if (!Str.starts_with(Prefix: "0x" )) { |
556 | reportTypeError(Str, TypeName: "address" ); |
557 | return std::nullopt; |
558 | } |
559 | uint64_t Addr; |
560 | if (Str.drop_front(N: 2).getAsInteger(Radix: 16, Result&: Addr)) { |
561 | reportTypeError(Str, TypeName: "address" ); |
562 | return std::nullopt; |
563 | } |
564 | return Addr; |
565 | } |
566 | |
567 | // Parse a module ID (%i in the spec). |
568 | std::optional<uint64_t> MarkupFilter::parseModuleID(StringRef Str) const { |
569 | uint64_t ID; |
570 | if (Str.getAsInteger(Radix: 0, Result&: ID)) { |
571 | reportTypeError(Str, TypeName: "module ID" ); |
572 | return std::nullopt; |
573 | } |
574 | return ID; |
575 | } |
576 | |
577 | // Parse a size (%i in the spec). |
578 | std::optional<uint64_t> MarkupFilter::parseSize(StringRef Str) const { |
579 | uint64_t ID; |
580 | if (Str.getAsInteger(Radix: 0, Result&: ID)) { |
581 | reportTypeError(Str, TypeName: "size" ); |
582 | return std::nullopt; |
583 | } |
584 | return ID; |
585 | } |
586 | |
587 | // Parse a frame number (%i in the spec). |
588 | std::optional<uint64_t> MarkupFilter::(StringRef Str) const { |
589 | uint64_t ID; |
590 | if (Str.getAsInteger(Radix: 10, Result&: ID)) { |
591 | reportTypeError(Str, TypeName: "frame number" ); |
592 | return std::nullopt; |
593 | } |
594 | return ID; |
595 | } |
596 | |
597 | // Parse a build ID (%x in the spec). |
598 | object::BuildID MarkupFilter::parseBuildID(StringRef Str) const { |
599 | object::BuildID BID = llvm::object::parseBuildID(Str); |
600 | if (BID.empty()) |
601 | reportTypeError(Str, TypeName: "build ID" ); |
602 | return BID; |
603 | } |
604 | |
605 | // Parses the mode string for an mmap element. |
606 | std::optional<std::string> MarkupFilter::parseMode(StringRef Str) const { |
607 | if (Str.empty()) { |
608 | reportTypeError(Str, TypeName: "mode" ); |
609 | return std::nullopt; |
610 | } |
611 | |
612 | // Pop off each of r/R, w/W, and x/X from the front, in that order. |
613 | StringRef Remainder = Str; |
614 | Remainder.consume_front_insensitive(Prefix: "r" ); |
615 | Remainder.consume_front_insensitive(Prefix: "w" ); |
616 | Remainder.consume_front_insensitive(Prefix: "x" ); |
617 | |
618 | // If anything remains, then the string wasn't a mode. |
619 | if (!Remainder.empty()) { |
620 | reportTypeError(Str, TypeName: "mode" ); |
621 | return std::nullopt; |
622 | } |
623 | |
624 | // Normalize the mode. |
625 | return Str.lower(); |
626 | } |
627 | |
628 | std::optional<MarkupFilter::PCType> |
629 | MarkupFilter::parsePCType(StringRef Str) const { |
630 | std::optional<MarkupFilter::PCType> Type = |
631 | StringSwitch<std::optional<MarkupFilter::PCType>>(Str) |
632 | .Case(S: "ra" , Value: MarkupFilter::PCType::ReturnAddress) |
633 | .Case(S: "pc" , Value: MarkupFilter::PCType::PreciseCode) |
634 | .Default(Value: std::nullopt); |
635 | if (!Type) |
636 | reportTypeError(Str, TypeName: "PC type" ); |
637 | return Type; |
638 | } |
639 | |
640 | bool MarkupFilter::checkTag(const MarkupNode &Node) const { |
641 | if (any_of(Range: Node.Tag, P: [](char C) { return C < 'a' || C > 'z'; })) { |
642 | WithColor::error(OS&: errs()) << "tags must be all lowercase characters\n" ; |
643 | reportLocation(Loc: Node.Tag.begin()); |
644 | return false; |
645 | } |
646 | return true; |
647 | } |
648 | |
649 | bool MarkupFilter::checkNumFields(const MarkupNode &Element, |
650 | size_t Size) const { |
651 | if (Element.Fields.size() != Size) { |
652 | bool Warn = Element.Fields.size() > Size; |
653 | WithColor(errs(), Warn ? HighlightColor::Warning : HighlightColor::Error) |
654 | << (Warn ? "warning: " : "error: " ) << "expected " << Size |
655 | << " field(s); found " << Element.Fields.size() << "\n" ; |
656 | reportLocation(Loc: Element.Tag.end()); |
657 | return Warn; |
658 | } |
659 | return true; |
660 | } |
661 | |
662 | bool MarkupFilter::checkNumFieldsAtLeast(const MarkupNode &Element, |
663 | size_t Size) const { |
664 | if (Element.Fields.size() < Size) { |
665 | WithColor::error(OS&: errs()) |
666 | << "expected at least " << Size << " field(s); found " |
667 | << Element.Fields.size() << "\n" ; |
668 | reportLocation(Loc: Element.Tag.end()); |
669 | return false; |
670 | } |
671 | return true; |
672 | } |
673 | |
674 | void MarkupFilter::warnNumFieldsAtMost(const MarkupNode &Element, |
675 | size_t Size) const { |
676 | if (Element.Fields.size() <= Size) |
677 | return; |
678 | WithColor::warning(OS&: errs()) |
679 | << "expected at most " << Size << " field(s); found " |
680 | << Element.Fields.size() << "\n" ; |
681 | reportLocation(Loc: Element.Tag.end()); |
682 | } |
683 | |
684 | void MarkupFilter::reportTypeError(StringRef Str, StringRef TypeName) const { |
685 | WithColor::error(OS&: errs()) << "expected " << TypeName << "; found '" << Str |
686 | << "'\n" ; |
687 | reportLocation(Loc: Str.begin()); |
688 | } |
689 | |
690 | // Prints two lines that point out the given location in the current Line using |
691 | // a caret. The iterator must be within the bounds of the most recent line |
692 | // passed to beginLine(). |
693 | void MarkupFilter::reportLocation(StringRef::iterator Loc) const { |
694 | errs() << Line; |
695 | WithColor(errs().indent(NumSpaces: Loc - StringRef(Line).begin()), |
696 | HighlightColor::String) |
697 | << '^'; |
698 | errs() << '\n'; |
699 | } |
700 | |
701 | // Checks for an existing mmap that overlaps the given one and returns a |
702 | // pointer to one of them. |
703 | const MarkupFilter::MMap * |
704 | MarkupFilter::getOverlappingMMap(const MMap &Map) const { |
705 | // If the given map contains the start of another mmap, they overlap. |
706 | auto I = MMaps.upper_bound(x: Map.Addr); |
707 | if (I != MMaps.end() && Map.contains(Addr: I->second.Addr)) |
708 | return &I->second; |
709 | |
710 | // If no element starts inside the given mmap, the only possible overlap would |
711 | // be if the preceding mmap contains the start point of the given mmap. |
712 | if (I != MMaps.begin()) { |
713 | --I; |
714 | if (I->second.contains(Addr: Map.Addr)) |
715 | return &I->second; |
716 | } |
717 | return nullptr; |
718 | } |
719 | |
720 | // Returns the MMap that contains the given address or nullptr if none. |
721 | const MarkupFilter::MMap *MarkupFilter::getContainingMMap(uint64_t Addr) const { |
722 | // Find the first mmap starting >= Addr. |
723 | auto I = MMaps.lower_bound(x: Addr); |
724 | if (I != MMaps.end() && I->second.contains(Addr)) |
725 | return &I->second; |
726 | |
727 | // The previous mmap is the last one starting < Addr. |
728 | if (I == MMaps.begin()) |
729 | return nullptr; |
730 | --I; |
731 | return I->second.contains(Addr) ? &I->second : nullptr; |
732 | } |
733 | |
734 | uint64_t MarkupFilter::adjustAddr(uint64_t Addr, PCType Type) const { |
735 | // Decrementing return addresses by one moves them into the call instruction. |
736 | // The address doesn't have to be the start of the call instruction, just some |
737 | // byte on the inside. Subtracting one avoids needing detailed instruction |
738 | // length information here. |
739 | return Type == MarkupFilter::PCType::ReturnAddress ? Addr - 1 : Addr; |
740 | } |
741 | |
742 | StringRef MarkupFilter::lineEnding() const { |
743 | return StringRef(Line).ends_with(Suffix: "\r\n" ) ? "\r\n" : "\n" ; |
744 | } |
745 | |
746 | bool MarkupFilter::MMap::contains(uint64_t Addr) const { |
747 | return this->Addr <= Addr && Addr < this->Addr + Size; |
748 | } |
749 | |
750 | // Returns the module-relative address for a given virtual address. |
751 | uint64_t MarkupFilter::MMap::getModuleRelativeAddr(uint64_t Addr) const { |
752 | return Addr - this->Addr + ModuleRelativeAddr; |
753 | } |
754 | |