1 //===-- TraceDumper.cpp ---------------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
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"
19 using namespace lldb_private
;
23 /// The given string or \b std::nullopt if it's empty.
24 static std::optional
<const char *> ToOptionalString(const char *s
) {
30 static const char *GetModuleName(const SymbolContext
&sc
) {
33 return sc
.module_sp
->GetFileSpec().GetFilename().AsCString();
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
)
42 return GetModuleName(item
.symbol_info
->sc
);
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;
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
) {
58 if (a
.column
!= b
.column
)
60 return a
.GetFile() == b
.GetFile();
63 /// Compare the symbol contexts of the provided \a SymbolInfo
67 /// \a true if both instructions belong to the same scope level analized
68 /// in the following order:
72 /// - inlined function
73 /// - source line info
75 IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo
&prev_insn
,
76 const TraceDumper::SymbolInfo
&insn
,
77 bool check_source_line_info
= true) {
79 if (insn
.sc
.module_sp
!= prev_insn
.sc
.module_sp
)
83 if (insn
.sc
.symbol
!= prev_insn
.sc
.symbol
)
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
)
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()
98 if (inline_block_a
!= inline_block_b
)
102 if (!check_source_line_info
)
105 const bool curr_line_valid
= IsLineEntryValid(insn
.sc
.line_entry
);
106 const bool prev_line_valid
= IsLineEntryValid(prev_insn
.sc
.line_entry
);
107 if (curr_line_valid
&& prev_line_valid
)
108 return FileLineAndColumnMatches(insn
.sc
.line_entry
,
109 prev_insn
.sc
.line_entry
);
110 return curr_line_valid
== prev_line_valid
;
113 class OutputWriterCLI
: public TraceDumper::OutputWriter
{
115 OutputWriterCLI(Stream
&s
, const TraceDumperOptions
&options
, Thread
&thread
)
116 : m_s(s
), m_options(options
) {
117 m_s
.Format("thread #{0}: tid = {1}\n", thread
.GetIndexID(), thread
.GetID());
120 void NoMoreData() override
{ m_s
<< " no more data\n"; }
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("\n[call tree #{0}]\n", i
);
126 DumpFunctionCallTree(*forest
[i
]);
130 void TraceItem(const TraceDumper::TraceItem
&item
) override
{
131 if (item
.symbol_info
) {
132 if (!item
.prev_symbol_info
||
133 !IsSameInstructionSymbolContext(*item
.prev_symbol_info
,
134 *item
.symbol_info
)) {
136 const char *module_name
= GetModuleName(item
);
139 else if (!item
.symbol_info
->sc
.function
&& !item
.symbol_info
->sc
.symbol
)
140 m_s
.Format("{0}`(none)", module_name
);
142 item
.symbol_info
->sc
.DumpStopContext(
143 &m_s
, item
.symbol_info
->exe_ctx
.GetTargetPtr(),
144 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);
153 if (item
.error
&& !m_was_prev_instruction_an_error
)
154 m_s
<< " ...missing instructions\n";
156 m_s
.Format(" {0}: ", item
.id
);
158 if (m_options
.show_timestamps
) {
159 m_s
.Format("[{0}] ", item
.timestamp
160 ? formatv("{0:3} ns", *item
.timestamp
).str()
165 m_s
<< "(event) " << TraceCursor::EventKindToString(*item
.event
);
166 switch (*item
.event
) {
167 case eTraceEventCPUChanged
:
168 m_s
.Format(" [new CPU={0}]",
169 item
.cpu_id
? std::to_string(*item
.cpu_id
) : "unavailable");
171 case eTraceEventHWClockTick
:
172 m_s
.Format(" [{0}]", item
.hw_clock
? std::to_string(*item
.hw_clock
)
175 case eTraceEventDisabledHW
:
176 case eTraceEventDisabledSW
:
178 case eTraceEventSyncPoint
:
179 m_s
.Format(" [{0}]", item
.sync_point_metadata
);
182 } else if (item
.error
) {
183 m_s
<< "(error) " << *item
.error
;
185 m_s
.Format("{0:x+16}", item
.load_address
);
186 if (item
.symbol_info
&& item
.symbol_info
->instruction
) {
188 item
.symbol_info
->instruction
->Dump(
189 &m_s
, /*max_opcode_byte_size=*/0,
190 /*show_address=*/false,
191 /*show_bytes=*/false, m_options
.show_control_flow_kind
,
192 &item
.symbol_info
->exe_ctx
, &item
.symbol_info
->sc
,
193 /*prev_sym_ctx=*/nullptr,
194 /*disassembly_addr_format=*/nullptr,
195 /*max_address_text_size=*/0);
199 m_was_prev_instruction_an_error
= (bool)item
.error
;
205 DumpSegmentContext(const TraceDumper::FunctionCall::TracedSegment
&segment
) {
206 if (segment
.GetOwningCall().IsError()) {
207 m_s
<< "<tracing errors>";
211 const SymbolContext
&first_sc
= segment
.GetFirstInstructionSymbolInfo().sc
;
212 first_sc
.DumpStopContext(
213 &m_s
, segment
.GetFirstInstructionSymbolInfo().exe_ctx
.GetTargetPtr(),
214 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);
220 const SymbolContext
&last_sc
= segment
.GetLastInstructionSymbolInfo().sc
;
221 if (IsLineEntryValid(first_sc
.line_entry
) &&
222 IsLineEntryValid(last_sc
.line_entry
)) {
223 m_s
.Format("{0}:{1}", last_sc
.line_entry
.line
, last_sc
.line_entry
.column
);
225 last_sc
.DumpStopContext(
226 &m_s
, segment
.GetFirstInstructionSymbolInfo().exe_ctx
.GetTargetPtr(),
227 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);
235 void DumpUntracedContext(const TraceDumper::FunctionCall
&function_call
) {
236 if (function_call
.IsError()) {
237 m_s
<< "tracing error";
239 const SymbolContext
&sc
= function_call
.GetSymbolInfo().sc
;
241 const char *module_name
= GetModuleName(sc
);
244 else if (!sc
.function
&& !sc
.symbol
)
245 m_s
<< module_name
<< "`(none)";
247 m_s
<< module_name
<< "`" << sc
.GetFunctionName().AsCString();
250 void DumpFunctionCallTree(const TraceDumper::FunctionCall
&function_call
) {
251 if (function_call
.GetUntracedPrefixSegment()) {
253 DumpUntracedContext(function_call
);
257 DumpFunctionCallTree(function_call
.GetUntracedPrefixSegment()->GetNestedCall());
261 for (const TraceDumper::FunctionCall::TracedSegment
&segment
:
262 function_call
.GetTracedSegments()) {
264 DumpSegmentContext(segment
);
265 m_s
.Format(" [{0}, {1}]\n", segment
.GetFirstInstructionID(),
266 segment
.GetLastInstructionID());
268 segment
.IfNestedCall([&](const TraceDumper::FunctionCall
&nested_call
) {
270 DumpFunctionCallTree(nested_call
);
277 TraceDumperOptions m_options
;
278 bool m_was_prev_instruction_an_error
= false;
281 class OutputWriterJSON
: public TraceDumper::OutputWriter
{
283 error_message: string
287 "tsc"?: string decimal,
292 "tsc"?: string decimal,
294 "loadAddress": string decimal,
296 "hwClock"?: string decimal,
297 "syncPointMetadata"?: string,
298 "timestamp_ns"?: string decimal,
305 "controlFlowKind"?: string,
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) {
316 ~OutputWriterJSON() { m_j
.arrayEnd(); }
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([&] { DumpFunctionCallTree(*forest
[i
]); });
325 void DumpFunctionCallTree(const TraceDumper::FunctionCall
&function_call
) {
326 if (function_call
.GetUntracedPrefixSegment()) {
327 m_j
.attributeObject("untracedPrefixSegment", [&] {
328 m_j
.attributeObject("nestedCall", [&] {
329 DumpFunctionCallTree(
330 function_call
.GetUntracedPrefixSegment()->GetNestedCall());
335 if (!function_call
.GetTracedSegments().empty()) {
336 m_j
.attributeArray("tracedSegments", [&] {
337 for (const TraceDumper::FunctionCall::TracedSegment
&segment
:
338 function_call
.GetTracedSegments()) {
340 m_j
.attribute("firstInstructionId",
341 std::to_string(segment
.GetFirstInstructionID()));
342 m_j
.attribute("lastInstructionId",
343 std::to_string(segment
.GetLastInstructionID()));
344 segment
.IfNestedCall(
345 [&](const TraceDumper::FunctionCall
&nested_call
) {
347 "nestedCall", [&] { DumpFunctionCallTree(nested_call
); });
355 void DumpEvent(const TraceDumper::TraceItem
&item
) {
356 m_j
.attribute("event", TraceCursor::EventKindToString(*item
.event
));
357 switch (*item
.event
) {
358 case eTraceEventCPUChanged
:
359 m_j
.attribute("cpuId", item
.cpu_id
);
361 case eTraceEventHWClockTick
:
362 m_j
.attribute("hwClock", item
.hw_clock
);
364 case eTraceEventDisabledHW
:
365 case eTraceEventDisabledSW
:
367 case eTraceEventSyncPoint
:
368 m_j
.attribute("syncPointMetadata", item
.sync_point_metadata
);
373 void DumpInstruction(const TraceDumper::TraceItem
&item
) {
374 m_j
.attribute("loadAddress", formatv("{0:x}", item
.load_address
));
375 if (item
.symbol_info
) {
376 m_j
.attribute("module", ToOptionalString(GetModuleName(item
)));
379 ToOptionalString(item
.symbol_info
->sc
.GetFunctionName().AsCString()));
381 if (lldb::InstructionSP instruction
= item
.symbol_info
->instruction
) {
382 ExecutionContext exe_ctx
= item
.symbol_info
->exe_ctx
;
383 m_j
.attribute("mnemonic",
384 ToOptionalString(instruction
->GetMnemonic(&exe_ctx
)));
385 if (m_options
.show_control_flow_kind
) {
386 lldb::InstructionControlFlowKind instruction_control_flow_kind
=
387 instruction
->GetControlFlowKind(&exe_ctx
);
388 m_j
.attribute("controlFlowKind",
390 Instruction::GetNameForInstructionControlFlowKind(
391 instruction_control_flow_kind
)));
395 if (IsLineEntryValid(item
.symbol_info
->sc
.line_entry
)) {
399 item
.symbol_info
->sc
.line_entry
.GetFile().GetPath().c_str()));
400 m_j
.attribute("line", item
.symbol_info
->sc
.line_entry
.line
);
401 m_j
.attribute("column", item
.symbol_info
->sc
.line_entry
.column
);
406 void TraceItem(const TraceDumper::TraceItem
&item
) override
{
408 m_j
.attribute("id", item
.id
);
409 if (m_options
.show_timestamps
)
410 m_j
.attribute("timestamp_ns", item
.timestamp
411 ? std::optional
<std::string
>(
412 std::to_string(*item
.timestamp
))
417 } else if (item
.error
) {
418 m_j
.attribute("error", *item
.error
);
420 DumpInstruction(item
);
427 TraceDumperOptions m_options
;
431 static std::unique_ptr
<TraceDumper::OutputWriter
>
432 CreateWriter(Stream
&s
, const TraceDumperOptions
&options
, Thread
&thread
) {
434 return std::unique_ptr
<TraceDumper::OutputWriter
>(
435 new OutputWriterJSON(s
, options
));
437 return std::unique_ptr
<TraceDumper::OutputWriter
>(
438 new OutputWriterCLI(s
, options
, thread
));
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
, m_options
, *m_cursor_sp
->GetExecutionContextRef().GetThreadSP())) {
448 m_cursor_sp
->GoToId(*m_options
.id
);
449 else if (m_options
.forwards
)
450 m_cursor_sp
->Seek(0, lldb::eTraceCursorSeekTypeBeginning
);
452 m_cursor_sp
->Seek(0, lldb::eTraceCursorSeekTypeEnd
);
454 m_cursor_sp
->SetForwards(m_options
.forwards
);
455 if (m_options
.skip
) {
456 m_cursor_sp
->Seek((m_options
.forwards
? 1 : -1) * *m_options
.skip
,
457 lldb::eTraceCursorSeekTypeCurrent
);
461 TraceDumper::TraceItem
TraceDumper::CreatRawTraceItem() {
463 item
.id
= m_cursor_sp
->GetId();
465 if (m_options
.show_timestamps
)
466 item
.timestamp
= m_cursor_sp
->GetWallClockTime();
470 /// Find the symbol context for the given address reusing the previous
471 /// instruction's symbol context when possible.
473 CalculateSymbolContext(const Address
&address
,
474 const SymbolContext
&prev_symbol_context
) {
475 lldb_private::AddressRange range
;
476 if (prev_symbol_context
.GetAddressRange(eSymbolContextEverything
, 0,
477 /*inline_block_range*/ true, range
) &&
478 range
.Contains(address
))
479 return prev_symbol_context
;
482 address
.CalculateSymbolContext(&sc
, eSymbolContextEverything
);
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(symbol_info
.address
))
496 return std::make_tuple(prev_symbol_info
.disassembler
, instruction
);
499 if (symbol_info
.sc
.function
) {
500 if (DisassemblerSP disassembler
=
501 symbol_info
.sc
.function
->GetInstructions(exe_ctx
, nullptr)) {
502 if (InstructionSP instruction
=
503 disassembler
->GetInstructionList().GetInstructionAtAddress(
504 symbol_info
.address
))
505 return std::make_tuple(disassembler
, instruction
);
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
= Disassembler::DisassembleRange(
514 arch
, /*plugin_name=*/nullptr,
515 /*flavor=*/nullptr, /*cpu=*/nullptr, /*features=*/nullptr, target
, range
);
516 return std::make_tuple(
518 disassembler
? disassembler
->GetInstructionList().GetInstructionAtAddress(
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_address
, exe_ctx
.GetTargetPtr());
530 CalculateSymbolContext(symbol_info
.address
, prev_symbol_info
.sc
);
531 std::tie(symbol_info
.disassembler
, symbol_info
.instruction
) =
532 CalculateDisass(symbol_info
, prev_symbol_info
, exe_ctx
);
536 std::optional
<lldb::user_id_t
> TraceDumper::DumpInstructions(size_t count
) {
537 ThreadSP thread_sp
= m_cursor_sp
->GetExecutionContextRef().GetThreadSP();
539 SymbolInfo prev_symbol_info
;
540 std::optional
<lldb::user_id_t
> last_id
;
542 ExecutionContext exe_ctx
;
543 thread_sp
->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx
);
545 for (size_t insn_seen
= 0; insn_seen
< count
&& m_cursor_sp
->HasValue();
546 m_cursor_sp
->Next()) {
548 last_id
= m_cursor_sp
->GetId();
549 TraceItem item
= CreatRawTraceItem();
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();
557 case eTraceEventHWClockTick
:
558 item
.hw_clock
= m_cursor_sp
->GetHWClock();
560 case eTraceEventDisabledHW
:
561 case eTraceEventDisabledSW
:
563 case eTraceEventSyncPoint
:
564 item
.sync_point_metadata
= m_cursor_sp
->GetSyncPointMetadata();
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
) {
573 item
.load_address
= m_cursor_sp
->GetLoadAddress();
575 if (!m_options
.raw
) {
576 SymbolInfo symbol_info
=
577 CalculateSymbolInfo(exe_ctx
, 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
;
582 m_writer_up
->TraceItem(item
);
585 if (!m_cursor_sp
->HasValue())
586 m_writer_up
->NoMoreData();
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
;
598 TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const {
599 return m_first_insn_id
;
603 TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const {
604 return m_last_insn_id
;
607 void TraceDumper::FunctionCall::TracedSegment::IfNestedCall(
608 std::function
<void(const FunctionCall
&function_call
)> callback
) const {
610 callback(*m_nested_call
);
613 const TraceDumper::FunctionCall
&
614 TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const {
615 return m_owning_call
;
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
>(cursor_sp
, symbol_info
);
623 m_nested_call
->SetParentCall(m_owning_call
);
624 return *m_nested_call
;
627 const TraceDumper::SymbolInfo
&
628 TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo()
630 return m_first_symbol_info
;
633 const TraceDumper::SymbolInfo
&
634 TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const {
635 return m_last_symbol_info
;
638 const TraceDumper::FunctionCall
&
639 TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const {
640 return *m_nested_call
;
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
);
650 void TraceDumper::FunctionCall::AppendSegment(
651 const TraceCursorSP
&cursor_sp
,
652 const TraceDumper::SymbolInfo
&symbol_info
) {
653 m_traced_segments
.emplace_back(cursor_sp
, symbol_info
, *this);
656 const TraceDumper::SymbolInfo
&
657 TraceDumper::FunctionCall::GetSymbolInfo() const {
658 return m_traced_segments
.back().GetLastInstructionSymbolInfo();
661 bool TraceDumper::FunctionCall::IsError() const { return m_is_error
; }
663 const std::deque
<TraceDumper::FunctionCall::TracedSegment
> &
664 TraceDumper::FunctionCall::GetTracedSegments() const {
665 return m_traced_segments
;
668 TraceDumper::FunctionCall::TracedSegment
&
669 TraceDumper::FunctionCall::GetLastTracedSegment() {
670 return m_traced_segments
.back();
673 const std::optional
<TraceDumper::FunctionCall::UntracedPrefixSegment
> &
674 TraceDumper::FunctionCall::GetUntracedPrefixSegment() const {
675 return m_untraced_prefix_segment
;
678 void TraceDumper::FunctionCall::SetUntracedPrefixSegment(
679 TraceDumper::FunctionCallUP
&&nested_call
) {
680 m_untraced_prefix_segment
.emplace(std::move(nested_call
));
683 TraceDumper::FunctionCall
*TraceDumper::FunctionCall::GetParentCall() const {
684 return m_parent_call
;
687 void TraceDumper::FunctionCall::SetParentCall(
688 TraceDumper::FunctionCall
&parent_call
) {
689 m_parent_call
= &parent_call
;
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.
696 /// \param[in] last_function_call
697 /// The function call that performs the return.
699 /// \param[in] symbol_info
700 /// The symbol information of the instruction after the return.
702 /// \param[in] cursor_sp
703 /// The cursor pointing to the instruction after the return.
705 /// \param[in,out] roots
706 /// The object owning the roots. It might be modified if a new root needs to
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
) {
716 // We omit the current node because we can't return to itself.
717 TraceDumper::FunctionCall
*ancestor
= last_function_call
.GetParentCall();
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(ancestor
->GetSymbolInfo(), 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
);
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
>(cursor_sp
, symbol_info
);
736 // This new root will own the previous root through an untraced prefix segment.
737 new_root
->SetUntracedPrefixSegment(std::move(roots
.back()));
739 // We update the roots container to point to the new root
740 roots
.emplace_back(std::move(new_root
));
741 return *roots
.back();
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
748 /// \param[in] exe_ctx
749 /// The exeuction context of the traced thread.
751 /// \param[in] last_function_call
752 /// The chronologically most recent function call before the new instruction.
754 /// \param[in] prev_symbol_info
755 /// The symbol information of the previous instruction in the trace.
757 /// \param[in] symbol_info
758 /// The symbol information of the new instruction.
760 /// \param[in] cursor_sp
761 /// The cursor pointing to the new instruction.
763 /// \param[in,out] roots
764 /// The object owning the roots. It might be modified if a new root needs to
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
778 std::make_unique
<TraceDumper::FunctionCall
>(cursor_sp
, symbol_info
));
779 return *roots
.back();
782 lldb_private::AddressRange range
;
783 if (symbol_info
.sc
.GetAddressRange(
784 eSymbolContextBlock
| eSymbolContextFunction
| eSymbolContextSymbol
,
785 0, /*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
);
794 if (IsSameInstructionSymbolContext(prev_symbol_info
, 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
,
800 return *last_function_call
;
802 // Now we are in a different symbol. Let's see if this is a return or a
804 const InstructionSP
&insn
= last_function_call
->GetLastTracedSegment()
805 .GetLastInstructionSymbolInfo()
807 InstructionControlFlowKind insn_kind
=
808 insn
? insn
->GetControlFlowKind(&exe_ctx
)
809 : eInstructionControlFlowKindOther
;
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
);
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
, symbol_info
, cursor_sp
, roots
);
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.
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
, symbol_info
, cursor_sp
, roots
);
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
);
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.
847 /// \param[in] last_function_call
848 /// The chronologically most recent function call before the new error.
850 /// \param[in] cursor_sp
851 /// The cursor pointing to the new error.
853 /// \param[in,out] roots
854 /// The object owning the roots. It might be modified if a new root needs to
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
, TraceDumper::SymbolInfo
{});
865 return *last_function_call
;
867 roots
.emplace_back(std::make_unique
<TraceDumper::FunctionCall
>(
868 cursor_sp
, TraceDumper::SymbolInfo
{}));
869 return *roots
.back();
873 static std::vector
<TraceDumper::FunctionCallUP
>
874 CreateFunctionCallForest(TraceCursorSP
&cursor_sp
,
875 const ExecutionContext
&exe_ctx
) {
877 std::vector
<TraceDumper::FunctionCallUP
> roots
;
878 TraceDumper::SymbolInfo prev_symbol_info
;
880 TraceDumper::FunctionCall
*last_function_call
= nullptr;
882 for (; cursor_sp
->HasValue(); cursor_sp
->Next()) {
883 if (cursor_sp
->IsError()) {
884 last_function_call
= &AppendErrorToFunctionCallForest(last_function_call
,
886 prev_symbol_info
= {};
887 } else if (cursor_sp
->IsInstruction()) {
888 TraceDumper::SymbolInfo symbol_info
= CalculateSymbolInfo(
889 exe_ctx
, cursor_sp
->GetLoadAddress(), prev_symbol_info
);
891 last_function_call
= &AppendInstructionToFunctionCallForest(
892 exe_ctx
, last_function_call
, prev_symbol_info
, symbol_info
, cursor_sp
,
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
= {};
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
);
912 m_writer_up
->FunctionCallForest(
913 CreateFunctionCallForest(m_cursor_sp
, exe_ctx
));