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 | |
18 | using namespace lldb; |
19 | using namespace lldb_private; |
20 | using namespace llvm; |
21 | |
22 | /// \return |
23 | /// The given string or \b std::nullopt if it's empty. |
24 | static std::optional<const char *> ToOptionalString(const char *s) { |
25 | if (!s) |
26 | return std::nullopt; |
27 | return s; |
28 | } |
29 | |
30 | static 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. |
39 | static 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. |
48 | static 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. |
55 | static 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 |
74 | static bool |
75 | IsSameInstructionSymbolContext(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 | |
113 | class OutputWriterCLI : public TraceDumper::OutputWriter { |
114 | public: |
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 | |
203 | private: |
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 | |
281 | class 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 | */ |
308 | public: |
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 | |
425 | private: |
426 | Stream &m_s; |
427 | TraceDumperOptions m_options; |
428 | json::OStream m_j; |
429 | }; |
430 | |
431 | static std::unique_ptr<TraceDumper::OutputWriter> |
432 | CreateWriter(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 | |
441 | TraceDumper::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 | |
461 | TraceDumper::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. |
472 | static SymbolContext |
473 | CalculateSymbolContext(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. |
488 | static std::tuple<DisassemblerSP, InstructionSP> |
489 | CalculateDisass(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 | |
523 | static TraceDumper::SymbolInfo |
524 | CalculateSymbolInfo(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 | |
536 | std::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 | |
590 | void 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 | |
597 | lldb::user_id_t |
598 | TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const { |
599 | return m_first_insn_id; |
600 | } |
601 | |
602 | lldb::user_id_t |
603 | TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const { |
604 | return m_last_insn_id; |
605 | } |
606 | |
607 | void 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 | |
613 | const TraceDumper::FunctionCall & |
614 | TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const { |
615 | return m_owning_call; |
616 | } |
617 | |
618 | TraceDumper::FunctionCall & |
619 | TraceDumper::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 | |
627 | const TraceDumper::SymbolInfo & |
628 | TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo() |
629 | const { |
630 | return m_first_symbol_info; |
631 | } |
632 | |
633 | const TraceDumper::SymbolInfo & |
634 | TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const { |
635 | return m_last_symbol_info; |
636 | } |
637 | |
638 | const TraceDumper::FunctionCall & |
639 | TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const { |
640 | return *m_nested_call; |
641 | } |
642 | |
643 | TraceDumper::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 | |
650 | void 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 | |
656 | const TraceDumper::SymbolInfo & |
657 | TraceDumper::FunctionCall::GetSymbolInfo() const { |
658 | return m_traced_segments.back().GetLastInstructionSymbolInfo(); |
659 | } |
660 | |
661 | bool TraceDumper::FunctionCall::IsError() const { return m_is_error; } |
662 | |
663 | const std::deque<TraceDumper::FunctionCall::TracedSegment> & |
664 | TraceDumper::FunctionCall::GetTracedSegments() const { |
665 | return m_traced_segments; |
666 | } |
667 | |
668 | TraceDumper::FunctionCall::TracedSegment & |
669 | TraceDumper::FunctionCall::GetLastTracedSegment() { |
670 | return m_traced_segments.back(); |
671 | } |
672 | |
673 | const std::optional<TraceDumper::FunctionCall::UntracedPrefixSegment> & |
674 | TraceDumper::FunctionCall::GetUntracedPrefixSegment() const { |
675 | return m_untraced_prefix_segment; |
676 | } |
677 | |
678 | void TraceDumper::FunctionCall::SetUntracedPrefixSegment( |
679 | TraceDumper::FunctionCallUP &&nested_call) { |
680 | m_untraced_prefix_segment.emplace(args: std::move(nested_call)); |
681 | } |
682 | |
683 | TraceDumper::FunctionCall *TraceDumper::FunctionCall::GetParentCall() const { |
684 | return m_parent_call; |
685 | } |
686 | |
687 | void 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 |
711 | static 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. |
769 | static 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. |
859 | TraceDumper::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 | |
873 | static std::vector<TraceDumper::FunctionCallUP> |
874 | CreateFunctionCallForest(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 | |
907 | void 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 | |