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
.file
== b
.file
;
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
.file
.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
) {
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 AddressRange
range(symbol_info
.address
, arch
.GetMaximumOpcodeByteSize());
512 DisassemblerSP disassembler
=
513 Disassembler::DisassembleRange(arch
, /*plugin_name*/ nullptr,
514 /*flavor*/ nullptr, target
, range
);
515 return std::make_tuple(
517 disassembler
? disassembler
->GetInstructionList().GetInstructionAtAddress(
522 static TraceDumper::SymbolInfo
523 CalculateSymbolInfo(const ExecutionContext
&exe_ctx
, lldb::addr_t load_address
,
524 const TraceDumper::SymbolInfo
&prev_symbol_info
) {
525 TraceDumper::SymbolInfo symbol_info
;
526 symbol_info
.exe_ctx
= exe_ctx
;
527 symbol_info
.address
.SetLoadAddress(load_address
, exe_ctx
.GetTargetPtr());
529 CalculateSymbolContext(symbol_info
.address
, prev_symbol_info
.sc
);
530 std::tie(symbol_info
.disassembler
, symbol_info
.instruction
) =
531 CalculateDisass(symbol_info
, prev_symbol_info
, exe_ctx
);
535 std::optional
<lldb::user_id_t
> TraceDumper::DumpInstructions(size_t count
) {
536 ThreadSP thread_sp
= m_cursor_sp
->GetExecutionContextRef().GetThreadSP();
538 SymbolInfo prev_symbol_info
;
539 std::optional
<lldb::user_id_t
> last_id
;
541 ExecutionContext exe_ctx
;
542 thread_sp
->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx
);
544 for (size_t insn_seen
= 0; insn_seen
< count
&& m_cursor_sp
->HasValue();
545 m_cursor_sp
->Next()) {
547 last_id
= m_cursor_sp
->GetId();
548 TraceItem item
= CreatRawTraceItem();
550 if (m_cursor_sp
->IsEvent() && m_options
.show_events
) {
551 item
.event
= m_cursor_sp
->GetEventType();
552 switch (*item
.event
) {
553 case eTraceEventCPUChanged
:
554 item
.cpu_id
= m_cursor_sp
->GetCPU();
556 case eTraceEventHWClockTick
:
557 item
.hw_clock
= m_cursor_sp
->GetHWClock();
559 case eTraceEventDisabledHW
:
560 case eTraceEventDisabledSW
:
562 case eTraceEventSyncPoint
:
563 item
.sync_point_metadata
= m_cursor_sp
->GetSyncPointMetadata();
566 m_writer_up
->TraceItem(item
);
567 } else if (m_cursor_sp
->IsError()) {
568 item
.error
= m_cursor_sp
->GetError();
569 m_writer_up
->TraceItem(item
);
570 } else if (m_cursor_sp
->IsInstruction() && !m_options
.only_events
) {
572 item
.load_address
= m_cursor_sp
->GetLoadAddress();
574 if (!m_options
.raw
) {
575 SymbolInfo symbol_info
=
576 CalculateSymbolInfo(exe_ctx
, item
.load_address
, prev_symbol_info
);
577 item
.prev_symbol_info
= prev_symbol_info
;
578 item
.symbol_info
= symbol_info
;
579 prev_symbol_info
= symbol_info
;
581 m_writer_up
->TraceItem(item
);
584 if (!m_cursor_sp
->HasValue())
585 m_writer_up
->NoMoreData();
589 void TraceDumper::FunctionCall::TracedSegment::AppendInsn(
590 const TraceCursorSP
&cursor_sp
,
591 const TraceDumper::SymbolInfo
&symbol_info
) {
592 m_last_insn_id
= cursor_sp
->GetId();
593 m_last_symbol_info
= symbol_info
;
597 TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const {
598 return m_first_insn_id
;
602 TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const {
603 return m_last_insn_id
;
606 void TraceDumper::FunctionCall::TracedSegment::IfNestedCall(
607 std::function
<void(const FunctionCall
&function_call
)> callback
) const {
609 callback(*m_nested_call
);
612 const TraceDumper::FunctionCall
&
613 TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const {
614 return m_owning_call
;
617 TraceDumper::FunctionCall
&
618 TraceDumper::FunctionCall::TracedSegment::CreateNestedCall(
619 const TraceCursorSP
&cursor_sp
,
620 const TraceDumper::SymbolInfo
&symbol_info
) {
621 m_nested_call
= std::make_unique
<FunctionCall
>(cursor_sp
, symbol_info
);
622 m_nested_call
->SetParentCall(m_owning_call
);
623 return *m_nested_call
;
626 const TraceDumper::SymbolInfo
&
627 TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo()
629 return m_first_symbol_info
;
632 const TraceDumper::SymbolInfo
&
633 TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const {
634 return m_last_symbol_info
;
637 const TraceDumper::FunctionCall
&
638 TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const {
639 return *m_nested_call
;
642 TraceDumper::FunctionCall::FunctionCall(
643 const TraceCursorSP
&cursor_sp
,
644 const TraceDumper::SymbolInfo
&symbol_info
) {
645 m_is_error
= cursor_sp
->IsError();
646 AppendSegment(cursor_sp
, symbol_info
);
649 void TraceDumper::FunctionCall::AppendSegment(
650 const TraceCursorSP
&cursor_sp
,
651 const TraceDumper::SymbolInfo
&symbol_info
) {
652 m_traced_segments
.emplace_back(cursor_sp
, symbol_info
, *this);
655 const TraceDumper::SymbolInfo
&
656 TraceDumper::FunctionCall::GetSymbolInfo() const {
657 return m_traced_segments
.back().GetLastInstructionSymbolInfo();
660 bool TraceDumper::FunctionCall::IsError() const { return m_is_error
; }
662 const std::deque
<TraceDumper::FunctionCall::TracedSegment
> &
663 TraceDumper::FunctionCall::GetTracedSegments() const {
664 return m_traced_segments
;
667 TraceDumper::FunctionCall::TracedSegment
&
668 TraceDumper::FunctionCall::GetLastTracedSegment() {
669 return m_traced_segments
.back();
672 const std::optional
<TraceDumper::FunctionCall::UntracedPrefixSegment
> &
673 TraceDumper::FunctionCall::GetUntracedPrefixSegment() const {
674 return m_untraced_prefix_segment
;
677 void TraceDumper::FunctionCall::SetUntracedPrefixSegment(
678 TraceDumper::FunctionCallUP
&&nested_call
) {
679 m_untraced_prefix_segment
.emplace(std::move(nested_call
));
682 TraceDumper::FunctionCall
*TraceDumper::FunctionCall::GetParentCall() const {
683 return m_parent_call
;
686 void TraceDumper::FunctionCall::SetParentCall(
687 TraceDumper::FunctionCall
&parent_call
) {
688 m_parent_call
= &parent_call
;
691 /// Given an instruction that happens after a return, find the ancestor function
692 /// call that owns it. If this ancestor doesn't exist, create a new ancestor and
693 /// make it the root of the tree.
695 /// \param[in] last_function_call
696 /// The function call that performs the return.
698 /// \param[in] symbol_info
699 /// The symbol information of the instruction after the return.
701 /// \param[in] cursor_sp
702 /// The cursor pointing to the instruction after the return.
704 /// \param[in,out] roots
705 /// The object owning the roots. It might be modified if a new root needs to
709 /// A reference to the function call that owns the new instruction
710 static TraceDumper::FunctionCall
&AppendReturnedInstructionToFunctionCallForest(
711 TraceDumper::FunctionCall
&last_function_call
,
712 const TraceDumper::SymbolInfo
&symbol_info
, const TraceCursorSP
&cursor_sp
,
713 std::vector
<TraceDumper::FunctionCallUP
> &roots
) {
715 // We omit the current node because we can't return to itself.
716 TraceDumper::FunctionCall
*ancestor
= last_function_call
.GetParentCall();
718 for (; ancestor
; ancestor
= ancestor
->GetParentCall()) {
719 // This loop traverses the tree until it finds a call that we can return to.
720 if (IsSameInstructionSymbolContext(ancestor
->GetSymbolInfo(), symbol_info
,
721 /*check_source_line_info=*/false)) {
722 // We returned to this symbol, so we are assuming we are returning there
723 // Note: If this is not robust enough, we should actually check if we
724 // returning to the instruction that follows the last instruction from
725 // that call, as that's the behavior of CALL instructions.
726 ancestor
->AppendSegment(cursor_sp
, symbol_info
);
731 // We didn't find the call we were looking for, so we now create a synthetic
732 // one that will contain the new instruction in its first traced segment.
733 TraceDumper::FunctionCallUP new_root
=
734 std::make_unique
<TraceDumper::FunctionCall
>(cursor_sp
, symbol_info
);
735 // This new root will own the previous root through an untraced prefix segment.
736 new_root
->SetUntracedPrefixSegment(std::move(roots
.back()));
738 // We update the roots container to point to the new root
739 roots
.emplace_back(std::move(new_root
));
740 return *roots
.back();
743 /// Append an instruction to a function call forest. The new instruction might
744 /// be appended to the current segment, to a new nest call, or return to an
747 /// \param[in] exe_ctx
748 /// The exeuction context of the traced thread.
750 /// \param[in] last_function_call
751 /// The chronologically most recent function call before the new instruction.
753 /// \param[in] prev_symbol_info
754 /// The symbol information of the previous instruction in the trace.
756 /// \param[in] symbol_info
757 /// The symbol information of the new instruction.
759 /// \param[in] cursor_sp
760 /// The cursor pointing to the new instruction.
762 /// \param[in,out] roots
763 /// The object owning the roots. It might be modified if a new root needs to
767 /// A reference to the function call that owns the new instruction.
768 static TraceDumper::FunctionCall
&AppendInstructionToFunctionCallForest(
769 const ExecutionContext
&exe_ctx
,
770 TraceDumper::FunctionCall
*last_function_call
,
771 const TraceDumper::SymbolInfo
&prev_symbol_info
,
772 const TraceDumper::SymbolInfo
&symbol_info
, const TraceCursorSP
&cursor_sp
,
773 std::vector
<TraceDumper::FunctionCallUP
> &roots
) {
774 if (!last_function_call
|| last_function_call
->IsError()) {
775 // We create a brand new root
777 std::make_unique
<TraceDumper::FunctionCall
>(cursor_sp
, symbol_info
));
778 return *roots
.back();
782 if (symbol_info
.sc
.GetAddressRange(
783 eSymbolContextBlock
| eSymbolContextFunction
| eSymbolContextSymbol
,
784 0, /*inline_block_range*/ true, range
)) {
785 if (range
.GetBaseAddress() == symbol_info
.address
) {
786 // Our instruction is the first instruction of a function. This has
787 // to be a call. This should also identify if a trampoline or the linker
788 // is making a call using a non-CALL instruction.
789 return last_function_call
->GetLastTracedSegment().CreateNestedCall(
790 cursor_sp
, symbol_info
);
793 if (IsSameInstructionSymbolContext(prev_symbol_info
, symbol_info
,
794 /*check_source_line_info=*/false)) {
795 // We are still in the same function. This can't be a call because otherwise
796 // we would be in the first instruction of the symbol.
797 last_function_call
->GetLastTracedSegment().AppendInsn(cursor_sp
,
799 return *last_function_call
;
801 // Now we are in a different symbol. Let's see if this is a return or a
803 const InstructionSP
&insn
= last_function_call
->GetLastTracedSegment()
804 .GetLastInstructionSymbolInfo()
806 InstructionControlFlowKind insn_kind
=
807 insn
? insn
->GetControlFlowKind(&exe_ctx
)
808 : eInstructionControlFlowKindOther
;
811 case lldb::eInstructionControlFlowKindCall
:
812 case lldb::eInstructionControlFlowKindFarCall
: {
813 // This is a regular call
814 return last_function_call
->GetLastTracedSegment().CreateNestedCall(
815 cursor_sp
, symbol_info
);
817 case lldb::eInstructionControlFlowKindFarReturn
:
818 case lldb::eInstructionControlFlowKindReturn
: {
819 // We should have caught most trampolines and linker functions earlier, so
820 // let's assume this is a regular return.
821 return AppendReturnedInstructionToFunctionCallForest(
822 *last_function_call
, symbol_info
, cursor_sp
, roots
);
825 // we changed symbols not using a call or return and we are not in the
826 // beginning of a symbol, so this should be something very artificial
827 // or maybe a jump to some label in the middle of it section.
829 // We first check if it's a return from an inline method
830 if (prev_symbol_info
.sc
.block
&&
831 prev_symbol_info
.sc
.block
->GetContainingInlinedBlock()) {
832 return AppendReturnedInstructionToFunctionCallForest(
833 *last_function_call
, symbol_info
, cursor_sp
, roots
);
835 // Now We assume it's a call. We should revisit this in the future.
836 // Ideally we should be able to decide whether to create a new tree,
837 // or go deeper or higher in the stack.
838 return last_function_call
->GetLastTracedSegment().CreateNestedCall(
839 cursor_sp
, symbol_info
);
843 /// Append an error to a function call forest. The new error might be appended
844 /// to the current segment if it contains errors or will create a new root.
846 /// \param[in] last_function_call
847 /// The chronologically most recent function call before the new error.
849 /// \param[in] cursor_sp
850 /// The cursor pointing to the new error.
852 /// \param[in,out] roots
853 /// The object owning the roots. It might be modified if a new root needs to
857 /// A reference to the function call that owns the new error.
858 TraceDumper::FunctionCall
&AppendErrorToFunctionCallForest(
859 TraceDumper::FunctionCall
*last_function_call
, TraceCursorSP
&cursor_sp
,
860 std::vector
<TraceDumper::FunctionCallUP
> &roots
) {
861 if (last_function_call
&& last_function_call
->IsError()) {
862 last_function_call
->GetLastTracedSegment().AppendInsn(
863 cursor_sp
, TraceDumper::SymbolInfo
{});
864 return *last_function_call
;
866 roots
.emplace_back(std::make_unique
<TraceDumper::FunctionCall
>(
867 cursor_sp
, TraceDumper::SymbolInfo
{}));
868 return *roots
.back();
872 static std::vector
<TraceDumper::FunctionCallUP
>
873 CreateFunctionCallForest(TraceCursorSP
&cursor_sp
,
874 const ExecutionContext
&exe_ctx
) {
876 std::vector
<TraceDumper::FunctionCallUP
> roots
;
877 TraceDumper::SymbolInfo prev_symbol_info
;
879 TraceDumper::FunctionCall
*last_function_call
= nullptr;
881 for (; cursor_sp
->HasValue(); cursor_sp
->Next()) {
882 if (cursor_sp
->IsError()) {
883 last_function_call
= &AppendErrorToFunctionCallForest(last_function_call
,
885 prev_symbol_info
= {};
886 } else if (cursor_sp
->IsInstruction()) {
887 TraceDumper::SymbolInfo symbol_info
= CalculateSymbolInfo(
888 exe_ctx
, cursor_sp
->GetLoadAddress(), prev_symbol_info
);
890 last_function_call
= &AppendInstructionToFunctionCallForest(
891 exe_ctx
, last_function_call
, prev_symbol_info
, symbol_info
, cursor_sp
,
893 prev_symbol_info
= symbol_info
;
894 } else if (cursor_sp
->GetEventType() == eTraceEventCPUChanged
) {
895 // TODO: In case of a CPU change, we create a new root because we haven't
896 // investigated yet if a call tree can safely continue or if interrupts
897 // could have polluted the original call tree.
898 last_function_call
= nullptr;
899 prev_symbol_info
= {};
906 void TraceDumper::DumpFunctionCalls() {
907 ThreadSP thread_sp
= m_cursor_sp
->GetExecutionContextRef().GetThreadSP();
908 ExecutionContext exe_ctx
;
909 thread_sp
->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx
);
911 m_writer_up
->FunctionCallForest(
912 CreateFunctionCallForest(m_cursor_sp
, exe_ctx
));