1//===-- TraceDumper.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#include "lldb/Target/TraceDumper.h"
10#include "lldb/Core/Module.h"
11#include "lldb/Symbol/CompileUnit.h"
12#include "lldb/Symbol/Function.h"
13#include "lldb/Target/ExecutionContext.h"
14#include "lldb/Target/Process.h"
15#include "lldb/Target/SectionLoadList.h"
16#include <optional>
17
18using namespace lldb;
19using namespace lldb_private;
20using namespace llvm;
21
22/// \return
23/// The given string or \b std::nullopt if it's empty.
24static std::optional<const char *> ToOptionalString(const char *s) {
25 if (!s)
26 return std::nullopt;
27 return s;
28}
29
30static const char *GetModuleName(const SymbolContext &sc) {
31 if (!sc.module_sp)
32 return nullptr;
33 return sc.module_sp->GetFileSpec().GetFilename().AsCString();
34}
35
36/// \return
37/// The module name (basename if the module is a file, or the actual name if
38/// it's a virtual module), or \b nullptr if no name nor module was found.
39static const char *GetModuleName(const TraceDumper::TraceItem &item) {
40 if (!item.symbol_info)
41 return nullptr;
42 return GetModuleName(sc: item.symbol_info->sc);
43}
44
45// This custom LineEntry validator is neded because some line_entries have
46// 0 as line, which is meaningless. Notice that LineEntry::IsValid only
47// checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX.
48static bool IsLineEntryValid(const LineEntry &line_entry) {
49 return line_entry.IsValid() && line_entry.line > 0;
50}
51
52/// \return
53/// \b true if the provided line entries match line, column and source file.
54/// This function assumes that the line entries are valid.
55static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
56 if (a.line != b.line)
57 return false;
58 if (a.column != b.column)
59 return false;
60 return a.GetFile() == b.GetFile();
61}
62
63/// Compare the symbol contexts of the provided \a SymbolInfo
64/// objects.
65///
66/// \return
67/// \a true if both instructions belong to the same scope level analized
68/// in the following order:
69/// - module
70/// - symbol
71/// - function
72/// - inlined function
73/// - source line info
74static bool
75IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo &prev_insn,
76 const TraceDumper::SymbolInfo &insn,
77 bool check_source_line_info = true) {
78 // module checks
79 if (insn.sc.module_sp != prev_insn.sc.module_sp)
80 return false;
81
82 // symbol checks
83 if (insn.sc.symbol != prev_insn.sc.symbol)
84 return false;
85
86 // function checks
87 if (!insn.sc.function && !prev_insn.sc.function)
88 return true; // This means two dangling instruction in the same module. We
89 // can assume they are part of the same unnamed symbol
90 else if (insn.sc.function != prev_insn.sc.function)
91 return false;
92
93 Block *inline_block_a =
94 insn.sc.block ? insn.sc.block->GetContainingInlinedBlock() : nullptr;
95 Block *inline_block_b = prev_insn.sc.block
96 ? prev_insn.sc.block->GetContainingInlinedBlock()
97 : nullptr;
98 if (inline_block_a != inline_block_b)
99 return false;
100
101 // line entry checks
102 if (!check_source_line_info)
103 return true;
104
105 const bool curr_line_valid = IsLineEntryValid(line_entry: insn.sc.line_entry);
106 const bool prev_line_valid = IsLineEntryValid(line_entry: prev_insn.sc.line_entry);
107 if (curr_line_valid && prev_line_valid)
108 return FileLineAndColumnMatches(a: insn.sc.line_entry,
109 b: prev_insn.sc.line_entry);
110 return curr_line_valid == prev_line_valid;
111}
112
113class OutputWriterCLI : public TraceDumper::OutputWriter {
114public:
115 OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread)
116 : m_s(s), m_options(options) {
117 m_s.Format(format: "thread #{0}: tid = {1}\n", args: thread.GetIndexID(), args: thread.GetID());
118 };
119
120 void NoMoreData() override { m_s << " no more data\n"; }
121
122 void FunctionCallForest(
123 const std::vector<TraceDumper::FunctionCallUP> &forest) override {
124 for (size_t i = 0; i < forest.size(); i++) {
125 m_s.Format(format: "\n[call tree #{0}]\n", args&: i);
126 DumpFunctionCallTree(function_call: *forest[i]);
127 }
128 }
129
130 void TraceItem(const TraceDumper::TraceItem &item) override {
131 if (item.symbol_info) {
132 if (!item.prev_symbol_info ||
133 !IsSameInstructionSymbolContext(prev_insn: *item.prev_symbol_info,
134 insn: *item.symbol_info)) {
135 m_s << " ";
136 const char *module_name = GetModuleName(item);
137 if (!module_name)
138 m_s << "(none)";
139 else if (!item.symbol_info->sc.function && !item.symbol_info->sc.symbol)
140 m_s.Format(format: "{0}`(none)", args&: module_name);
141 else
142 item.symbol_info->sc.DumpStopContext(
143 s: &m_s, exe_scope: item.symbol_info->exe_ctx.GetTargetPtr(),
144 so_addr: item.symbol_info->address,
145 /*show_fullpaths=*/false,
146 /*show_module=*/true, /*show_inlined_frames=*/false,
147 /*show_function_arguments=*/true,
148 /*show_function_name=*/true);
149 m_s << "\n";
150 }
151 }
152
153 if (item.error && !m_was_prev_instruction_an_error)
154 m_s << " ...missing instructions\n";
155
156 m_s.Format(format: " {0}: ", args: item.id);
157
158 if (m_options.show_timestamps) {
159 m_s.Format(format: "[{0}] ", args: item.timestamp
160 ? formatv(Fmt: "{0:3} ns", Vals: *item.timestamp).str()
161 : "unavailable");
162 }
163
164 if (item.event) {
165 m_s << "(event) " << TraceCursor::EventKindToString(event_kind: *item.event);
166 switch (*item.event) {
167 case eTraceEventCPUChanged:
168 m_s.Format(format: " [new CPU={0}]",
169 args: item.cpu_id ? std::to_string(val: *item.cpu_id) : "unavailable");
170 break;
171 case eTraceEventHWClockTick:
172 m_s.Format(format: " [{0}]", args: item.hw_clock ? std::to_string(val: *item.hw_clock)
173 : "unavailable");
174 break;
175 case eTraceEventDisabledHW:
176 case eTraceEventDisabledSW:
177 break;
178 case eTraceEventSyncPoint:
179 m_s.Format(format: " [{0}]", args: item.sync_point_metadata);
180 break;
181 }
182 } else if (item.error) {
183 m_s << "(error) " << *item.error;
184 } else {
185 m_s.Format(format: "{0:x+16}", args: item.load_address);
186 if (item.symbol_info && item.symbol_info->instruction) {
187 m_s << " ";
188 item.symbol_info->instruction->Dump(
189 s: &m_s, /*max_opcode_byte_size=*/0,
190 /*show_address=*/false,
191 /*show_bytes=*/false, show_control_flow_kind: m_options.show_control_flow_kind,
192 exe_ctx: &item.symbol_info->exe_ctx, sym_ctx: &item.symbol_info->sc,
193 /*prev_sym_ctx=*/nullptr,
194 /*disassembly_addr_format=*/nullptr,
195 /*max_address_text_size=*/0);
196 }
197 }
198
199 m_was_prev_instruction_an_error = (bool)item.error;
200 m_s << "\n";
201 }
202
203private:
204 void
205 DumpSegmentContext(const TraceDumper::FunctionCall::TracedSegment &segment) {
206 if (segment.GetOwningCall().IsError()) {
207 m_s << "<tracing errors>";
208 return;
209 }
210
211 const SymbolContext &first_sc = segment.GetFirstInstructionSymbolInfo().sc;
212 first_sc.DumpStopContext(
213 s: &m_s, exe_scope: segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(),
214 so_addr: segment.GetFirstInstructionSymbolInfo().address,
215 /*show_fullpaths=*/false,
216 /*show_module=*/true, /*show_inlined_frames=*/false,
217 /*show_function_arguments=*/true,
218 /*show_function_name=*/true);
219 m_s << " to ";
220 const SymbolContext &last_sc = segment.GetLastInstructionSymbolInfo().sc;
221 if (IsLineEntryValid(line_entry: first_sc.line_entry) &&
222 IsLineEntryValid(line_entry: last_sc.line_entry)) {
223 m_s.Format(format: "{0}:{1}", args: last_sc.line_entry.line, args: last_sc.line_entry.column);
224 } else {
225 last_sc.DumpStopContext(
226 s: &m_s, exe_scope: segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(),
227 so_addr: segment.GetLastInstructionSymbolInfo().address,
228 /*show_fullpaths=*/false,
229 /*show_module=*/false, /*show_inlined_frames=*/false,
230 /*show_function_arguments=*/false,
231 /*show_function_name=*/false);
232 }
233 }
234
235 void DumpUntracedContext(const TraceDumper::FunctionCall &function_call) {
236 if (function_call.IsError()) {
237 m_s << "tracing error";
238 }
239 const SymbolContext &sc = function_call.GetSymbolInfo().sc;
240
241 const char *module_name = GetModuleName(sc);
242 if (!module_name)
243 m_s << "(none)";
244 else if (!sc.function && !sc.symbol)
245 m_s << module_name << "`(none)";
246 else
247 m_s << module_name << "`" << sc.GetFunctionName().AsCString();
248 }
249
250 void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) {
251 if (function_call.GetUntracedPrefixSegment()) {
252 m_s.Indent();
253 DumpUntracedContext(function_call);
254 m_s << "\n";
255
256 m_s.IndentMore();
257 DumpFunctionCallTree(function_call: function_call.GetUntracedPrefixSegment()->GetNestedCall());
258 m_s.IndentLess();
259 }
260
261 for (const TraceDumper::FunctionCall::TracedSegment &segment :
262 function_call.GetTracedSegments()) {
263 m_s.Indent();
264 DumpSegmentContext(segment);
265 m_s.Format(format: " [{0}, {1}]\n", args: segment.GetFirstInstructionID(),
266 args: segment.GetLastInstructionID());
267
268 segment.IfNestedCall(callback: [&](const TraceDumper::FunctionCall &nested_call) {
269 m_s.IndentMore();
270 DumpFunctionCallTree(function_call: nested_call);
271 m_s.IndentLess();
272 });
273 }
274 }
275
276 Stream &m_s;
277 TraceDumperOptions m_options;
278 bool m_was_prev_instruction_an_error = false;
279};
280
281class OutputWriterJSON : public TraceDumper::OutputWriter {
282 /* schema:
283 error_message: string
284 | {
285 "event": string,
286 "id": decimal,
287 "tsc"?: string decimal,
288 "cpuId"? decimal,
289 } | {
290 "error": string,
291 "id": decimal,
292 "tsc"?: string decimal,
293 | {
294 "loadAddress": string decimal,
295 "id": decimal,
296 "hwClock"?: string decimal,
297 "syncPointMetadata"?: string,
298 "timestamp_ns"?: string decimal,
299 "module"?: string,
300 "symbol"?: string,
301 "line"?: decimal,
302 "column"?: decimal,
303 "source"?: string,
304 "mnemonic"?: string,
305 "controlFlowKind"?: string,
306 }
307 */
308public:
309 OutputWriterJSON(Stream &s, const TraceDumperOptions &options)
310 : m_s(s), m_options(options),
311 m_j(m_s.AsRawOstream(),
312 /*IndentSize=*/options.pretty_print_json ? 2 : 0) {
313 m_j.arrayBegin();
314 };
315
316 ~OutputWriterJSON() { m_j.arrayEnd(); }
317
318 void FunctionCallForest(
319 const std::vector<TraceDumper::FunctionCallUP> &forest) override {
320 for (size_t i = 0; i < forest.size(); i++) {
321 m_j.object(Contents: [&] { DumpFunctionCallTree(function_call: *forest[i]); });
322 }
323 }
324
325 void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) {
326 if (function_call.GetUntracedPrefixSegment()) {
327 m_j.attributeObject(Key: "untracedPrefixSegment", Contents: [&] {
328 m_j.attributeObject(Key: "nestedCall", Contents: [&] {
329 DumpFunctionCallTree(
330 function_call: function_call.GetUntracedPrefixSegment()->GetNestedCall());
331 });
332 });
333 }
334
335 if (!function_call.GetTracedSegments().empty()) {
336 m_j.attributeArray(Key: "tracedSegments", Contents: [&] {
337 for (const TraceDumper::FunctionCall::TracedSegment &segment :
338 function_call.GetTracedSegments()) {
339 m_j.object(Contents: [&] {
340 m_j.attribute(Key: "firstInstructionId",
341 Contents: std::to_string(val: segment.GetFirstInstructionID()));
342 m_j.attribute(Key: "lastInstructionId",
343 Contents: std::to_string(val: segment.GetLastInstructionID()));
344 segment.IfNestedCall(
345 callback: [&](const TraceDumper::FunctionCall &nested_call) {
346 m_j.attributeObject(
347 Key: "nestedCall", Contents: [&] { DumpFunctionCallTree(function_call: nested_call); });
348 });
349 });
350 }
351 });
352 }
353 }
354
355 void DumpEvent(const TraceDumper::TraceItem &item) {
356 m_j.attribute(Key: "event", Contents: TraceCursor::EventKindToString(event_kind: *item.event));
357 switch (*item.event) {
358 case eTraceEventCPUChanged:
359 m_j.attribute(Key: "cpuId", Contents: item.cpu_id);
360 break;
361 case eTraceEventHWClockTick:
362 m_j.attribute(Key: "hwClock", Contents: item.hw_clock);
363 break;
364 case eTraceEventDisabledHW:
365 case eTraceEventDisabledSW:
366 break;
367 case eTraceEventSyncPoint:
368 m_j.attribute(Key: "syncPointMetadata", Contents: item.sync_point_metadata);
369 break;
370 }
371 }
372
373 void DumpInstruction(const TraceDumper::TraceItem &item) {
374 m_j.attribute(Key: "loadAddress", Contents: formatv(Fmt: "{0:x}", Vals: item.load_address));
375 if (item.symbol_info) {
376 m_j.attribute(Key: "module", Contents: ToOptionalString(s: GetModuleName(item)));
377 m_j.attribute(
378 Key: "symbol",
379 Contents: ToOptionalString(s: item.symbol_info->sc.GetFunctionName().AsCString()));
380
381 if (lldb::InstructionSP instruction = item.symbol_info->instruction) {
382 ExecutionContext exe_ctx = item.symbol_info->exe_ctx;
383 m_j.attribute(Key: "mnemonic",
384 Contents: ToOptionalString(s: instruction->GetMnemonic(exe_ctx: &exe_ctx)));
385 if (m_options.show_control_flow_kind) {
386 lldb::InstructionControlFlowKind instruction_control_flow_kind =
387 instruction->GetControlFlowKind(exe_ctx: &exe_ctx);
388 m_j.attribute(Key: "controlFlowKind",
389 Contents: ToOptionalString(
390 s: Instruction::GetNameForInstructionControlFlowKind(
391 instruction_control_flow_kind)));
392 }
393 }
394
395 if (IsLineEntryValid(line_entry: item.symbol_info->sc.line_entry)) {
396 m_j.attribute(
397 Key: "source",
398 Contents: ToOptionalString(
399 s: item.symbol_info->sc.line_entry.GetFile().GetPath().c_str()));
400 m_j.attribute(Key: "line", Contents: item.symbol_info->sc.line_entry.line);
401 m_j.attribute(Key: "column", Contents: item.symbol_info->sc.line_entry.column);
402 }
403 }
404 }
405
406 void TraceItem(const TraceDumper::TraceItem &item) override {
407 m_j.object(Contents: [&] {
408 m_j.attribute(Key: "id", Contents: item.id);
409 if (m_options.show_timestamps)
410 m_j.attribute(Key: "timestamp_ns", Contents: item.timestamp
411 ? std::optional<std::string>(
412 std::to_string(val: *item.timestamp))
413 : std::nullopt);
414
415 if (item.event) {
416 DumpEvent(item);
417 } else if (item.error) {
418 m_j.attribute(Key: "error", Contents: *item.error);
419 } else {
420 DumpInstruction(item);
421 }
422 });
423 }
424
425private:
426 Stream &m_s;
427 TraceDumperOptions m_options;
428 json::OStream m_j;
429};
430
431static std::unique_ptr<TraceDumper::OutputWriter>
432CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) {
433 if (options.json)
434 return std::unique_ptr<TraceDumper::OutputWriter>(
435 new OutputWriterJSON(s, options));
436 else
437 return std::unique_ptr<TraceDumper::OutputWriter>(
438 new OutputWriterCLI(s, options, thread));
439}
440
441TraceDumper::TraceDumper(lldb::TraceCursorSP cursor_sp, Stream &s,
442 const TraceDumperOptions &options)
443 : m_cursor_sp(std::move(cursor_sp)), m_options(options),
444 m_writer_up(CreateWriter(
445 s, options: m_options, thread&: *m_cursor_sp->GetExecutionContextRef().GetThreadSP())) {
446
447 if (m_options.id)
448 m_cursor_sp->GoToId(id: *m_options.id);
449 else if (m_options.forwards)
450 m_cursor_sp->Seek(offset: 0, origin: lldb::eTraceCursorSeekTypeBeginning);
451 else
452 m_cursor_sp->Seek(offset: 0, origin: lldb::eTraceCursorSeekTypeEnd);
453
454 m_cursor_sp->SetForwards(m_options.forwards);
455 if (m_options.skip) {
456 m_cursor_sp->Seek(offset: (m_options.forwards ? 1 : -1) * *m_options.skip,
457 origin: lldb::eTraceCursorSeekTypeCurrent);
458 }
459}
460
461TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() {
462 TraceItem item = {};
463 item.id = m_cursor_sp->GetId();
464
465 if (m_options.show_timestamps)
466 item.timestamp = m_cursor_sp->GetWallClockTime();
467 return item;
468}
469
470/// Find the symbol context for the given address reusing the previous
471/// instruction's symbol context when possible.
472static SymbolContext
473CalculateSymbolContext(const Address &address,
474 const SymbolContext &prev_symbol_context) {
475 lldb_private::AddressRange range;
476 if (prev_symbol_context.GetAddressRange(scope: eSymbolContextEverything, range_idx: 0,
477 /*inline_block_range*/ use_inline_block_range: true, range) &&
478 range.Contains(so_addr: address))
479 return prev_symbol_context;
480
481 SymbolContext sc;
482 address.CalculateSymbolContext(sc: &sc, resolve_scope: eSymbolContextEverything);
483 return sc;
484}
485
486/// Find the disassembler for the given address reusing the previous
487/// instruction's disassembler when possible.
488static std::tuple<DisassemblerSP, InstructionSP>
489CalculateDisass(const TraceDumper::SymbolInfo &symbol_info,
490 const TraceDumper::SymbolInfo &prev_symbol_info,
491 const ExecutionContext &exe_ctx) {
492 if (prev_symbol_info.disassembler) {
493 if (InstructionSP instruction =
494 prev_symbol_info.disassembler->GetInstructionList()
495 .GetInstructionAtAddress(addr: symbol_info.address))
496 return std::make_tuple(args: prev_symbol_info.disassembler, args&: instruction);
497 }
498
499 if (symbol_info.sc.function) {
500 if (DisassemblerSP disassembler =
501 symbol_info.sc.function->GetInstructions(exe_ctx, flavor: nullptr)) {
502 if (InstructionSP instruction =
503 disassembler->GetInstructionList().GetInstructionAtAddress(
504 addr: symbol_info.address))
505 return std::make_tuple(args&: disassembler, args&: instruction);
506 }
507 }
508 // We fallback to a single instruction disassembler
509 Target &target = exe_ctx.GetTargetRef();
510 const ArchSpec arch = target.GetArchitecture();
511 lldb_private::AddressRange range(symbol_info.address,
512 arch.GetMaximumOpcodeByteSize());
513 DisassemblerSP disassembler =
514 Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr,
515 /*flavor*/ nullptr, target, disasm_range: range);
516 return std::make_tuple(
517 args&: disassembler,
518 args: disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress(
519 addr: symbol_info.address)
520 : InstructionSP());
521}
522
523static TraceDumper::SymbolInfo
524CalculateSymbolInfo(const ExecutionContext &exe_ctx, lldb::addr_t load_address,
525 const TraceDumper::SymbolInfo &prev_symbol_info) {
526 TraceDumper::SymbolInfo symbol_info;
527 symbol_info.exe_ctx = exe_ctx;
528 symbol_info.address.SetLoadAddress(load_addr: load_address, target: exe_ctx.GetTargetPtr());
529 symbol_info.sc =
530 CalculateSymbolContext(address: symbol_info.address, prev_symbol_context: prev_symbol_info.sc);
531 std::tie(args&: symbol_info.disassembler, args&: symbol_info.instruction) =
532 CalculateDisass(symbol_info, prev_symbol_info, exe_ctx);
533 return symbol_info;
534}
535
536std::optional<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) {
537 ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP();
538
539 SymbolInfo prev_symbol_info;
540 std::optional<lldb::user_id_t> last_id;
541
542 ExecutionContext exe_ctx;
543 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);
544
545 for (size_t insn_seen = 0; insn_seen < count && m_cursor_sp->HasValue();
546 m_cursor_sp->Next()) {
547
548 last_id = m_cursor_sp->GetId();
549 TraceItem item = CreatRawTraceItem();
550
551 if (m_cursor_sp->IsEvent() && m_options.show_events) {
552 item.event = m_cursor_sp->GetEventType();
553 switch (*item.event) {
554 case eTraceEventCPUChanged:
555 item.cpu_id = m_cursor_sp->GetCPU();
556 break;
557 case eTraceEventHWClockTick:
558 item.hw_clock = m_cursor_sp->GetHWClock();
559 break;
560 case eTraceEventDisabledHW:
561 case eTraceEventDisabledSW:
562 break;
563 case eTraceEventSyncPoint:
564 item.sync_point_metadata = m_cursor_sp->GetSyncPointMetadata();
565 break;
566 }
567 m_writer_up->TraceItem(item);
568 } else if (m_cursor_sp->IsError()) {
569 item.error = m_cursor_sp->GetError();
570 m_writer_up->TraceItem(item);
571 } else if (m_cursor_sp->IsInstruction() && !m_options.only_events) {
572 insn_seen++;
573 item.load_address = m_cursor_sp->GetLoadAddress();
574
575 if (!m_options.raw) {
576 SymbolInfo symbol_info =
577 CalculateSymbolInfo(exe_ctx, load_address: item.load_address, prev_symbol_info);
578 item.prev_symbol_info = prev_symbol_info;
579 item.symbol_info = symbol_info;
580 prev_symbol_info = symbol_info;
581 }
582 m_writer_up->TraceItem(item);
583 }
584 }
585 if (!m_cursor_sp->HasValue())
586 m_writer_up->NoMoreData();
587 return last_id;
588}
589
590void TraceDumper::FunctionCall::TracedSegment::AppendInsn(
591 const TraceCursorSP &cursor_sp,
592 const TraceDumper::SymbolInfo &symbol_info) {
593 m_last_insn_id = cursor_sp->GetId();
594 m_last_symbol_info = symbol_info;
595}
596
597lldb::user_id_t
598TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const {
599 return m_first_insn_id;
600}
601
602lldb::user_id_t
603TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const {
604 return m_last_insn_id;
605}
606
607void TraceDumper::FunctionCall::TracedSegment::IfNestedCall(
608 std::function<void(const FunctionCall &function_call)> callback) const {
609 if (m_nested_call)
610 callback(*m_nested_call);
611}
612
613const TraceDumper::FunctionCall &
614TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const {
615 return m_owning_call;
616}
617
618TraceDumper::FunctionCall &
619TraceDumper::FunctionCall::TracedSegment::CreateNestedCall(
620 const TraceCursorSP &cursor_sp,
621 const TraceDumper::SymbolInfo &symbol_info) {
622 m_nested_call = std::make_unique<FunctionCall>(args: cursor_sp, args: symbol_info);
623 m_nested_call->SetParentCall(m_owning_call);
624 return *m_nested_call;
625}
626
627const TraceDumper::SymbolInfo &
628TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo()
629 const {
630 return m_first_symbol_info;
631}
632
633const TraceDumper::SymbolInfo &
634TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const {
635 return m_last_symbol_info;
636}
637
638const TraceDumper::FunctionCall &
639TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const {
640 return *m_nested_call;
641}
642
643TraceDumper::FunctionCall::FunctionCall(
644 const TraceCursorSP &cursor_sp,
645 const TraceDumper::SymbolInfo &symbol_info) {
646 m_is_error = cursor_sp->IsError();
647 AppendSegment(cursor_sp, symbol_info);
648}
649
650void TraceDumper::FunctionCall::AppendSegment(
651 const TraceCursorSP &cursor_sp,
652 const TraceDumper::SymbolInfo &symbol_info) {
653 m_traced_segments.emplace_back(args: cursor_sp, args: symbol_info, args&: *this);
654}
655
656const TraceDumper::SymbolInfo &
657TraceDumper::FunctionCall::GetSymbolInfo() const {
658 return m_traced_segments.back().GetLastInstructionSymbolInfo();
659}
660
661bool TraceDumper::FunctionCall::IsError() const { return m_is_error; }
662
663const std::deque<TraceDumper::FunctionCall::TracedSegment> &
664TraceDumper::FunctionCall::GetTracedSegments() const {
665 return m_traced_segments;
666}
667
668TraceDumper::FunctionCall::TracedSegment &
669TraceDumper::FunctionCall::GetLastTracedSegment() {
670 return m_traced_segments.back();
671}
672
673const std::optional<TraceDumper::FunctionCall::UntracedPrefixSegment> &
674TraceDumper::FunctionCall::GetUntracedPrefixSegment() const {
675 return m_untraced_prefix_segment;
676}
677
678void TraceDumper::FunctionCall::SetUntracedPrefixSegment(
679 TraceDumper::FunctionCallUP &&nested_call) {
680 m_untraced_prefix_segment.emplace(args: std::move(nested_call));
681}
682
683TraceDumper::FunctionCall *TraceDumper::FunctionCall::GetParentCall() const {
684 return m_parent_call;
685}
686
687void TraceDumper::FunctionCall::SetParentCall(
688 TraceDumper::FunctionCall &parent_call) {
689 m_parent_call = &parent_call;
690}
691
692/// Given an instruction that happens after a return, find the ancestor function
693/// call that owns it. If this ancestor doesn't exist, create a new ancestor and
694/// make it the root of the tree.
695///
696/// \param[in] last_function_call
697/// The function call that performs the return.
698///
699/// \param[in] symbol_info
700/// The symbol information of the instruction after the return.
701///
702/// \param[in] cursor_sp
703/// The cursor pointing to the instruction after the return.
704///
705/// \param[in,out] roots
706/// The object owning the roots. It might be modified if a new root needs to
707/// be created.
708///
709/// \return
710/// A reference to the function call that owns the new instruction
711static TraceDumper::FunctionCall &AppendReturnedInstructionToFunctionCallForest(
712 TraceDumper::FunctionCall &last_function_call,
713 const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp,
714 std::vector<TraceDumper::FunctionCallUP> &roots) {
715
716 // We omit the current node because we can't return to itself.
717 TraceDumper::FunctionCall *ancestor = last_function_call.GetParentCall();
718
719 for (; ancestor; ancestor = ancestor->GetParentCall()) {
720 // This loop traverses the tree until it finds a call that we can return to.
721 if (IsSameInstructionSymbolContext(prev_insn: ancestor->GetSymbolInfo(), insn: symbol_info,
722 /*check_source_line_info=*/false)) {
723 // We returned to this symbol, so we are assuming we are returning there
724 // Note: If this is not robust enough, we should actually check if we
725 // returning to the instruction that follows the last instruction from
726 // that call, as that's the behavior of CALL instructions.
727 ancestor->AppendSegment(cursor_sp, symbol_info);
728 return *ancestor;
729 }
730 }
731
732 // We didn't find the call we were looking for, so we now create a synthetic
733 // one that will contain the new instruction in its first traced segment.
734 TraceDumper::FunctionCallUP new_root =
735 std::make_unique<TraceDumper::FunctionCall>(args: cursor_sp, args: symbol_info);
736 // This new root will own the previous root through an untraced prefix segment.
737 new_root->SetUntracedPrefixSegment(std::move(roots.back()));
738 roots.pop_back();
739 // We update the roots container to point to the new root
740 roots.emplace_back(args: std::move(new_root));
741 return *roots.back();
742}
743
744/// Append an instruction to a function call forest. The new instruction might
745/// be appended to the current segment, to a new nest call, or return to an
746/// ancestor call.
747///
748/// \param[in] exe_ctx
749/// The exeuction context of the traced thread.
750///
751/// \param[in] last_function_call
752/// The chronologically most recent function call before the new instruction.
753///
754/// \param[in] prev_symbol_info
755/// The symbol information of the previous instruction in the trace.
756///
757/// \param[in] symbol_info
758/// The symbol information of the new instruction.
759///
760/// \param[in] cursor_sp
761/// The cursor pointing to the new instruction.
762///
763/// \param[in,out] roots
764/// The object owning the roots. It might be modified if a new root needs to
765/// be created.
766///
767/// \return
768/// A reference to the function call that owns the new instruction.
769static TraceDumper::FunctionCall &AppendInstructionToFunctionCallForest(
770 const ExecutionContext &exe_ctx,
771 TraceDumper::FunctionCall *last_function_call,
772 const TraceDumper::SymbolInfo &prev_symbol_info,
773 const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp,
774 std::vector<TraceDumper::FunctionCallUP> &roots) {
775 if (!last_function_call || last_function_call->IsError()) {
776 // We create a brand new root
777 roots.emplace_back(
778 args: std::make_unique<TraceDumper::FunctionCall>(args: cursor_sp, args: symbol_info));
779 return *roots.back();
780 }
781
782 lldb_private::AddressRange range;
783 if (symbol_info.sc.GetAddressRange(
784 scope: eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol,
785 range_idx: 0, /*inline_block_range*/ use_inline_block_range: true, range)) {
786 if (range.GetBaseAddress() == symbol_info.address) {
787 // Our instruction is the first instruction of a function. This has
788 // to be a call. This should also identify if a trampoline or the linker
789 // is making a call using a non-CALL instruction.
790 return last_function_call->GetLastTracedSegment().CreateNestedCall(
791 cursor_sp, symbol_info);
792 }
793 }
794 if (IsSameInstructionSymbolContext(prev_insn: prev_symbol_info, insn: symbol_info,
795 /*check_source_line_info=*/false)) {
796 // We are still in the same function. This can't be a call because otherwise
797 // we would be in the first instruction of the symbol.
798 last_function_call->GetLastTracedSegment().AppendInsn(cursor_sp,
799 symbol_info);
800 return *last_function_call;
801 }
802 // Now we are in a different symbol. Let's see if this is a return or a
803 // call
804 const InstructionSP &insn = last_function_call->GetLastTracedSegment()
805 .GetLastInstructionSymbolInfo()
806 .instruction;
807 InstructionControlFlowKind insn_kind =
808 insn ? insn->GetControlFlowKind(exe_ctx: &exe_ctx)
809 : eInstructionControlFlowKindOther;
810
811 switch (insn_kind) {
812 case lldb::eInstructionControlFlowKindCall:
813 case lldb::eInstructionControlFlowKindFarCall: {
814 // This is a regular call
815 return last_function_call->GetLastTracedSegment().CreateNestedCall(
816 cursor_sp, symbol_info);
817 }
818 case lldb::eInstructionControlFlowKindFarReturn:
819 case lldb::eInstructionControlFlowKindReturn: {
820 // We should have caught most trampolines and linker functions earlier, so
821 // let's assume this is a regular return.
822 return AppendReturnedInstructionToFunctionCallForest(
823 last_function_call&: *last_function_call, symbol_info, cursor_sp, roots);
824 }
825 default:
826 // we changed symbols not using a call or return and we are not in the
827 // beginning of a symbol, so this should be something very artificial
828 // or maybe a jump to some label in the middle of it section.
829
830 // We first check if it's a return from an inline method
831 if (prev_symbol_info.sc.block &&
832 prev_symbol_info.sc.block->GetContainingInlinedBlock()) {
833 return AppendReturnedInstructionToFunctionCallForest(
834 last_function_call&: *last_function_call, symbol_info, cursor_sp, roots);
835 }
836 // Now We assume it's a call. We should revisit this in the future.
837 // Ideally we should be able to decide whether to create a new tree,
838 // or go deeper or higher in the stack.
839 return last_function_call->GetLastTracedSegment().CreateNestedCall(
840 cursor_sp, symbol_info);
841 }
842}
843
844/// Append an error to a function call forest. The new error might be appended
845/// to the current segment if it contains errors or will create a new root.
846///
847/// \param[in] last_function_call
848/// The chronologically most recent function call before the new error.
849///
850/// \param[in] cursor_sp
851/// The cursor pointing to the new error.
852///
853/// \param[in,out] roots
854/// The object owning the roots. It might be modified if a new root needs to
855/// be created.
856///
857/// \return
858/// A reference to the function call that owns the new error.
859TraceDumper::FunctionCall &AppendErrorToFunctionCallForest(
860 TraceDumper::FunctionCall *last_function_call, TraceCursorSP &cursor_sp,
861 std::vector<TraceDumper::FunctionCallUP> &roots) {
862 if (last_function_call && last_function_call->IsError()) {
863 last_function_call->GetLastTracedSegment().AppendInsn(
864 cursor_sp, symbol_info: TraceDumper::SymbolInfo{});
865 return *last_function_call;
866 } else {
867 roots.emplace_back(args: std::make_unique<TraceDumper::FunctionCall>(
868 args&: cursor_sp, args: TraceDumper::SymbolInfo{}));
869 return *roots.back();
870 }
871}
872
873static std::vector<TraceDumper::FunctionCallUP>
874CreateFunctionCallForest(TraceCursorSP &cursor_sp,
875 const ExecutionContext &exe_ctx) {
876
877 std::vector<TraceDumper::FunctionCallUP> roots;
878 TraceDumper::SymbolInfo prev_symbol_info;
879
880 TraceDumper::FunctionCall *last_function_call = nullptr;
881
882 for (; cursor_sp->HasValue(); cursor_sp->Next()) {
883 if (cursor_sp->IsError()) {
884 last_function_call = &AppendErrorToFunctionCallForest(last_function_call,
885 cursor_sp, roots);
886 prev_symbol_info = {};
887 } else if (cursor_sp->IsInstruction()) {
888 TraceDumper::SymbolInfo symbol_info = CalculateSymbolInfo(
889 exe_ctx, load_address: cursor_sp->GetLoadAddress(), prev_symbol_info);
890
891 last_function_call = &AppendInstructionToFunctionCallForest(
892 exe_ctx, last_function_call, prev_symbol_info, symbol_info, cursor_sp,
893 roots);
894 prev_symbol_info = symbol_info;
895 } else if (cursor_sp->GetEventType() == eTraceEventCPUChanged) {
896 // TODO: In case of a CPU change, we create a new root because we haven't
897 // investigated yet if a call tree can safely continue or if interrupts
898 // could have polluted the original call tree.
899 last_function_call = nullptr;
900 prev_symbol_info = {};
901 }
902 }
903
904 return roots;
905}
906
907void TraceDumper::DumpFunctionCalls() {
908 ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP();
909 ExecutionContext exe_ctx;
910 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);
911
912 m_writer_up->FunctionCallForest(
913 forest: CreateFunctionCallForest(cursor_sp&: m_cursor_sp, exe_ctx));
914}
915

source code of lldb/source/Target/TraceDumper.cpp