//===-- TraceDumper.cpp ---------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Target/TraceDumper.h" #include "lldb/Core/Module.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/Function.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" #include "lldb/Target/SectionLoadList.h" #include using namespace lldb; using namespace lldb_private; using namespace llvm; /// \return /// The given string or \b std::nullopt if it's empty. static std::optional ToOptionalString(const char *s) { if (!s) return std::nullopt; return s; } static const char *GetModuleName(const SymbolContext &sc) { if (!sc.module_sp) return nullptr; return sc.module_sp->GetFileSpec().GetFilename().AsCString(); } /// \return /// The module name (basename if the module is a file, or the actual name if /// it's a virtual module), or \b nullptr if no name nor module was found. static const char *GetModuleName(const TraceDumper::TraceItem &item) { if (!item.symbol_info) return nullptr; return GetModuleName(item.symbol_info->sc); } // This custom LineEntry validator is neded because some line_entries have // 0 as line, which is meaningless. Notice that LineEntry::IsValid only // checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX. static bool IsLineEntryValid(const LineEntry &line_entry) { return line_entry.IsValid() && line_entry.line > 0; } /// \return /// \b true if the provided line entries match line, column and source file. /// This function assumes that the line entries are valid. static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) { if (a.line != b.line) return false; if (a.column != b.column) return false; return a.GetFile() == b.GetFile(); } /// Compare the symbol contexts of the provided \a SymbolInfo /// objects. /// /// \return /// \a true if both instructions belong to the same scope level analized /// in the following order: /// - module /// - symbol /// - function /// - inlined function /// - source line info static bool IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo &prev_insn, const TraceDumper::SymbolInfo &insn, bool check_source_line_info = true) { // module checks if (insn.sc.module_sp != prev_insn.sc.module_sp) return false; // symbol checks if (insn.sc.symbol != prev_insn.sc.symbol) return false; // function checks if (!insn.sc.function && !prev_insn.sc.function) return true; // This means two dangling instruction in the same module. We // can assume they are part of the same unnamed symbol else if (insn.sc.function != prev_insn.sc.function) return false; Block *inline_block_a = insn.sc.block ? insn.sc.block->GetContainingInlinedBlock() : nullptr; Block *inline_block_b = prev_insn.sc.block ? prev_insn.sc.block->GetContainingInlinedBlock() : nullptr; if (inline_block_a != inline_block_b) return false; // line entry checks if (!check_source_line_info) return true; const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry); const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry); if (curr_line_valid && prev_line_valid) return FileLineAndColumnMatches(insn.sc.line_entry, prev_insn.sc.line_entry); return curr_line_valid == prev_line_valid; } class OutputWriterCLI : public TraceDumper::OutputWriter { public: OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread) : m_s(s), m_options(options) { m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID()); }; void NoMoreData() override { m_s << " no more data\n"; } void FunctionCallForest( const std::vector &forest) override { for (size_t i = 0; i < forest.size(); i++) { m_s.Format("\n[call tree #{0}]\n", i); DumpFunctionCallTree(*forest[i]); } } void TraceItem(const TraceDumper::TraceItem &item) override { if (item.symbol_info) { if (!item.prev_symbol_info || !IsSameInstructionSymbolContext(*item.prev_symbol_info, *item.symbol_info)) { m_s << " "; const char *module_name = GetModuleName(item); if (!module_name) m_s << "(none)"; else if (!item.symbol_info->sc.function && !item.symbol_info->sc.symbol) m_s.Format("{0}`(none)", module_name); else item.symbol_info->sc.DumpStopContext( &m_s, item.symbol_info->exe_ctx.GetTargetPtr(), item.symbol_info->address, /*show_fullpaths=*/false, /*show_module=*/true, /*show_inlined_frames=*/false, /*show_function_arguments=*/true, /*show_function_name=*/true); m_s << "\n"; } } if (item.error && !m_was_prev_instruction_an_error) m_s << " ...missing instructions\n"; m_s.Format(" {0}: ", item.id); if (m_options.show_timestamps) { m_s.Format("[{0}] ", item.timestamp ? formatv("{0:3} ns", *item.timestamp).str() : "unavailable"); } if (item.event) { m_s << "(event) " << TraceCursor::EventKindToString(*item.event); switch (*item.event) { case eTraceEventCPUChanged: m_s.Format(" [new CPU={0}]", item.cpu_id ? std::to_string(*item.cpu_id) : "unavailable"); break; case eTraceEventHWClockTick: m_s.Format(" [{0}]", item.hw_clock ? std::to_string(*item.hw_clock) : "unavailable"); break; case eTraceEventDisabledHW: case eTraceEventDisabledSW: break; case eTraceEventSyncPoint: m_s.Format(" [{0}]", item.sync_point_metadata); break; } } else if (item.error) { m_s << "(error) " << *item.error; } else { m_s.Format("{0:x+16}", item.load_address); if (item.symbol_info && item.symbol_info->instruction) { m_s << " "; item.symbol_info->instruction->Dump( &m_s, /*max_opcode_byte_size=*/0, /*show_address=*/false, /*show_bytes=*/false, m_options.show_control_flow_kind, &item.symbol_info->exe_ctx, &item.symbol_info->sc, /*prev_sym_ctx=*/nullptr, /*disassembly_addr_format=*/nullptr, /*max_address_text_size=*/0); } } m_was_prev_instruction_an_error = (bool)item.error; m_s << "\n"; } private: void DumpSegmentContext(const TraceDumper::FunctionCall::TracedSegment &segment) { if (segment.GetOwningCall().IsError()) { m_s << ""; return; } const SymbolContext &first_sc = segment.GetFirstInstructionSymbolInfo().sc; first_sc.DumpStopContext( &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(), segment.GetFirstInstructionSymbolInfo().address, /*show_fullpaths=*/false, /*show_module=*/true, /*show_inlined_frames=*/false, /*show_function_arguments=*/true, /*show_function_name=*/true); m_s << " to "; const SymbolContext &last_sc = segment.GetLastInstructionSymbolInfo().sc; if (IsLineEntryValid(first_sc.line_entry) && IsLineEntryValid(last_sc.line_entry)) { m_s.Format("{0}:{1}", last_sc.line_entry.line, last_sc.line_entry.column); } else { last_sc.DumpStopContext( &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(), segment.GetLastInstructionSymbolInfo().address, /*show_fullpaths=*/false, /*show_module=*/false, /*show_inlined_frames=*/false, /*show_function_arguments=*/false, /*show_function_name=*/false); } } void DumpUntracedContext(const TraceDumper::FunctionCall &function_call) { if (function_call.IsError()) { m_s << "tracing error"; } const SymbolContext &sc = function_call.GetSymbolInfo().sc; const char *module_name = GetModuleName(sc); if (!module_name) m_s << "(none)"; else if (!sc.function && !sc.symbol) m_s << module_name << "`(none)"; else m_s << module_name << "`" << sc.GetFunctionName().AsCString(); } void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) { if (function_call.GetUntracedPrefixSegment()) { m_s.Indent(); DumpUntracedContext(function_call); m_s << "\n"; m_s.IndentMore(); DumpFunctionCallTree(function_call.GetUntracedPrefixSegment()->GetNestedCall()); m_s.IndentLess(); } for (const TraceDumper::FunctionCall::TracedSegment &segment : function_call.GetTracedSegments()) { m_s.Indent(); DumpSegmentContext(segment); m_s.Format(" [{0}, {1}]\n", segment.GetFirstInstructionID(), segment.GetLastInstructionID()); segment.IfNestedCall([&](const TraceDumper::FunctionCall &nested_call) { m_s.IndentMore(); DumpFunctionCallTree(nested_call); m_s.IndentLess(); }); } } Stream &m_s; TraceDumperOptions m_options; bool m_was_prev_instruction_an_error = false; }; class OutputWriterJSON : public TraceDumper::OutputWriter { /* schema: error_message: string | { "event": string, "id": decimal, "tsc"?: string decimal, "cpuId"? decimal, } | { "error": string, "id": decimal, "tsc"?: string decimal, | { "loadAddress": string decimal, "id": decimal, "hwClock"?: string decimal, "syncPointMetadata"?: string, "timestamp_ns"?: string decimal, "module"?: string, "symbol"?: string, "line"?: decimal, "column"?: decimal, "source"?: string, "mnemonic"?: string, "controlFlowKind"?: string, } */ public: OutputWriterJSON(Stream &s, const TraceDumperOptions &options) : m_s(s), m_options(options), m_j(m_s.AsRawOstream(), /*IndentSize=*/options.pretty_print_json ? 2 : 0) { m_j.arrayBegin(); }; ~OutputWriterJSON() { m_j.arrayEnd(); } void FunctionCallForest( const std::vector &forest) override { for (size_t i = 0; i < forest.size(); i++) { m_j.object([&] { DumpFunctionCallTree(*forest[i]); }); } } void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) { if (function_call.GetUntracedPrefixSegment()) { m_j.attributeObject("untracedPrefixSegment", [&] { m_j.attributeObject("nestedCall", [&] { DumpFunctionCallTree( function_call.GetUntracedPrefixSegment()->GetNestedCall()); }); }); } if (!function_call.GetTracedSegments().empty()) { m_j.attributeArray("tracedSegments", [&] { for (const TraceDumper::FunctionCall::TracedSegment &segment : function_call.GetTracedSegments()) { m_j.object([&] { m_j.attribute("firstInstructionId", std::to_string(segment.GetFirstInstructionID())); m_j.attribute("lastInstructionId", std::to_string(segment.GetLastInstructionID())); segment.IfNestedCall( [&](const TraceDumper::FunctionCall &nested_call) { m_j.attributeObject( "nestedCall", [&] { DumpFunctionCallTree(nested_call); }); }); }); } }); } } void DumpEvent(const TraceDumper::TraceItem &item) { m_j.attribute("event", TraceCursor::EventKindToString(*item.event)); switch (*item.event) { case eTraceEventCPUChanged: m_j.attribute("cpuId", item.cpu_id); break; case eTraceEventHWClockTick: m_j.attribute("hwClock", item.hw_clock); break; case eTraceEventDisabledHW: case eTraceEventDisabledSW: break; case eTraceEventSyncPoint: m_j.attribute("syncPointMetadata", item.sync_point_metadata); break; } } void DumpInstruction(const TraceDumper::TraceItem &item) { m_j.attribute("loadAddress", formatv("{0:x}", item.load_address)); if (item.symbol_info) { m_j.attribute("module", ToOptionalString(GetModuleName(item))); m_j.attribute( "symbol", ToOptionalString(item.symbol_info->sc.GetFunctionName().AsCString())); if (lldb::InstructionSP instruction = item.symbol_info->instruction) { ExecutionContext exe_ctx = item.symbol_info->exe_ctx; m_j.attribute("mnemonic", ToOptionalString(instruction->GetMnemonic(&exe_ctx))); if (m_options.show_control_flow_kind) { lldb::InstructionControlFlowKind instruction_control_flow_kind = instruction->GetControlFlowKind(&exe_ctx); m_j.attribute("controlFlowKind", ToOptionalString( Instruction::GetNameForInstructionControlFlowKind( instruction_control_flow_kind))); } } if (IsLineEntryValid(item.symbol_info->sc.line_entry)) { m_j.attribute( "source", ToOptionalString( item.symbol_info->sc.line_entry.GetFile().GetPath().c_str())); m_j.attribute("line", item.symbol_info->sc.line_entry.line); m_j.attribute("column", item.symbol_info->sc.line_entry.column); } } } void TraceItem(const TraceDumper::TraceItem &item) override { m_j.object([&] { m_j.attribute("id", item.id); if (m_options.show_timestamps) m_j.attribute("timestamp_ns", item.timestamp ? std::optional( std::to_string(*item.timestamp)) : std::nullopt); if (item.event) { DumpEvent(item); } else if (item.error) { m_j.attribute("error", *item.error); } else { DumpInstruction(item); } }); } private: Stream &m_s; TraceDumperOptions m_options; json::OStream m_j; }; static std::unique_ptr CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) { if (options.json) return std::unique_ptr( new OutputWriterJSON(s, options)); else return std::unique_ptr( new OutputWriterCLI(s, options, thread)); } TraceDumper::TraceDumper(lldb::TraceCursorSP cursor_sp, Stream &s, const TraceDumperOptions &options) : m_cursor_sp(std::move(cursor_sp)), m_options(options), m_writer_up(CreateWriter( s, m_options, *m_cursor_sp->GetExecutionContextRef().GetThreadSP())) { if (m_options.id) m_cursor_sp->GoToId(*m_options.id); else if (m_options.forwards) m_cursor_sp->Seek(0, lldb::eTraceCursorSeekTypeBeginning); else m_cursor_sp->Seek(0, lldb::eTraceCursorSeekTypeEnd); m_cursor_sp->SetForwards(m_options.forwards); if (m_options.skip) { m_cursor_sp->Seek((m_options.forwards ? 1 : -1) * *m_options.skip, lldb::eTraceCursorSeekTypeCurrent); } } TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() { TraceItem item = {}; item.id = m_cursor_sp->GetId(); if (m_options.show_timestamps) item.timestamp = m_cursor_sp->GetWallClockTime(); return item; } /// Find the symbol context for the given address reusing the previous /// instruction's symbol context when possible. static SymbolContext CalculateSymbolContext(const Address &address, const SymbolContext &prev_symbol_context) { lldb_private::AddressRange range; if (prev_symbol_context.GetAddressRange(eSymbolContextEverything, 0, /*inline_block_range*/ true, range) && range.Contains(address)) return prev_symbol_context; SymbolContext sc; address.CalculateSymbolContext(&sc, eSymbolContextEverything); return sc; } /// Find the disassembler for the given address reusing the previous /// instruction's disassembler when possible. static std::tuple CalculateDisass(const TraceDumper::SymbolInfo &symbol_info, const TraceDumper::SymbolInfo &prev_symbol_info, const ExecutionContext &exe_ctx) { if (prev_symbol_info.disassembler) { if (InstructionSP instruction = prev_symbol_info.disassembler->GetInstructionList() .GetInstructionAtAddress(symbol_info.address)) return std::make_tuple(prev_symbol_info.disassembler, instruction); } if (symbol_info.sc.function) { if (DisassemblerSP disassembler = symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) { if (InstructionSP instruction = disassembler->GetInstructionList().GetInstructionAtAddress( symbol_info.address)) return std::make_tuple(disassembler, instruction); } } // We fallback to a single instruction disassembler Target &target = exe_ctx.GetTargetRef(); const ArchSpec arch = target.GetArchitecture(); lldb_private::AddressRange range(symbol_info.address, arch.GetMaximumOpcodeByteSize()); DisassemblerSP disassembler = Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, /*flavor*/ nullptr, target, range); return std::make_tuple( disassembler, disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress( symbol_info.address) : InstructionSP()); } static TraceDumper::SymbolInfo CalculateSymbolInfo(const ExecutionContext &exe_ctx, lldb::addr_t load_address, const TraceDumper::SymbolInfo &prev_symbol_info) { TraceDumper::SymbolInfo symbol_info; symbol_info.exe_ctx = exe_ctx; symbol_info.address.SetLoadAddress(load_address, exe_ctx.GetTargetPtr()); symbol_info.sc = CalculateSymbolContext(symbol_info.address, prev_symbol_info.sc); std::tie(symbol_info.disassembler, symbol_info.instruction) = CalculateDisass(symbol_info, prev_symbol_info, exe_ctx); return symbol_info; } std::optional TraceDumper::DumpInstructions(size_t count) { ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP(); SymbolInfo prev_symbol_info; std::optional last_id; ExecutionContext exe_ctx; thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx); for (size_t insn_seen = 0; insn_seen < count && m_cursor_sp->HasValue(); m_cursor_sp->Next()) { last_id = m_cursor_sp->GetId(); TraceItem item = CreatRawTraceItem(); if (m_cursor_sp->IsEvent() && m_options.show_events) { item.event = m_cursor_sp->GetEventType(); switch (*item.event) { case eTraceEventCPUChanged: item.cpu_id = m_cursor_sp->GetCPU(); break; case eTraceEventHWClockTick: item.hw_clock = m_cursor_sp->GetHWClock(); break; case eTraceEventDisabledHW: case eTraceEventDisabledSW: break; case eTraceEventSyncPoint: item.sync_point_metadata = m_cursor_sp->GetSyncPointMetadata(); break; } m_writer_up->TraceItem(item); } else if (m_cursor_sp->IsError()) { item.error = m_cursor_sp->GetError(); m_writer_up->TraceItem(item); } else if (m_cursor_sp->IsInstruction() && !m_options.only_events) { insn_seen++; item.load_address = m_cursor_sp->GetLoadAddress(); if (!m_options.raw) { SymbolInfo symbol_info = CalculateSymbolInfo(exe_ctx, item.load_address, prev_symbol_info); item.prev_symbol_info = prev_symbol_info; item.symbol_info = symbol_info; prev_symbol_info = symbol_info; } m_writer_up->TraceItem(item); } } if (!m_cursor_sp->HasValue()) m_writer_up->NoMoreData(); return last_id; } void TraceDumper::FunctionCall::TracedSegment::AppendInsn( const TraceCursorSP &cursor_sp, const TraceDumper::SymbolInfo &symbol_info) { m_last_insn_id = cursor_sp->GetId(); m_last_symbol_info = symbol_info; } lldb::user_id_t TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const { return m_first_insn_id; } lldb::user_id_t TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const { return m_last_insn_id; } void TraceDumper::FunctionCall::TracedSegment::IfNestedCall( std::function callback) const { if (m_nested_call) callback(*m_nested_call); } const TraceDumper::FunctionCall & TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const { return m_owning_call; } TraceDumper::FunctionCall & TraceDumper::FunctionCall::TracedSegment::CreateNestedCall( const TraceCursorSP &cursor_sp, const TraceDumper::SymbolInfo &symbol_info) { m_nested_call = std::make_unique(cursor_sp, symbol_info); m_nested_call->SetParentCall(m_owning_call); return *m_nested_call; } const TraceDumper::SymbolInfo & TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo() const { return m_first_symbol_info; } const TraceDumper::SymbolInfo & TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const { return m_last_symbol_info; } const TraceDumper::FunctionCall & TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const { return *m_nested_call; } TraceDumper::FunctionCall::FunctionCall( const TraceCursorSP &cursor_sp, const TraceDumper::SymbolInfo &symbol_info) { m_is_error = cursor_sp->IsError(); AppendSegment(cursor_sp, symbol_info); } void TraceDumper::FunctionCall::AppendSegment( const TraceCursorSP &cursor_sp, const TraceDumper::SymbolInfo &symbol_info) { m_traced_segments.emplace_back(cursor_sp, symbol_info, *this); } const TraceDumper::SymbolInfo & TraceDumper::FunctionCall::GetSymbolInfo() const { return m_traced_segments.back().GetLastInstructionSymbolInfo(); } bool TraceDumper::FunctionCall::IsError() const { return m_is_error; } const std::deque & TraceDumper::FunctionCall::GetTracedSegments() const { return m_traced_segments; } TraceDumper::FunctionCall::TracedSegment & TraceDumper::FunctionCall::GetLastTracedSegment() { return m_traced_segments.back(); } const std::optional & TraceDumper::FunctionCall::GetUntracedPrefixSegment() const { return m_untraced_prefix_segment; } void TraceDumper::FunctionCall::SetUntracedPrefixSegment( TraceDumper::FunctionCallUP &&nested_call) { m_untraced_prefix_segment.emplace(std::move(nested_call)); } TraceDumper::FunctionCall *TraceDumper::FunctionCall::GetParentCall() const { return m_parent_call; } void TraceDumper::FunctionCall::SetParentCall( TraceDumper::FunctionCall &parent_call) { m_parent_call = &parent_call; } /// Given an instruction that happens after a return, find the ancestor function /// call that owns it. If this ancestor doesn't exist, create a new ancestor and /// make it the root of the tree. /// /// \param[in] last_function_call /// The function call that performs the return. /// /// \param[in] symbol_info /// The symbol information of the instruction after the return. /// /// \param[in] cursor_sp /// The cursor pointing to the instruction after the return. /// /// \param[in,out] roots /// The object owning the roots. It might be modified if a new root needs to /// be created. /// /// \return /// A reference to the function call that owns the new instruction static TraceDumper::FunctionCall &AppendReturnedInstructionToFunctionCallForest( TraceDumper::FunctionCall &last_function_call, const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp, std::vector &roots) { // We omit the current node because we can't return to itself. TraceDumper::FunctionCall *ancestor = last_function_call.GetParentCall(); for (; ancestor; ancestor = ancestor->GetParentCall()) { // This loop traverses the tree until it finds a call that we can return to. if (IsSameInstructionSymbolContext(ancestor->GetSymbolInfo(), symbol_info, /*check_source_line_info=*/false)) { // We returned to this symbol, so we are assuming we are returning there // Note: If this is not robust enough, we should actually check if we // returning to the instruction that follows the last instruction from // that call, as that's the behavior of CALL instructions. ancestor->AppendSegment(cursor_sp, symbol_info); return *ancestor; } } // We didn't find the call we were looking for, so we now create a synthetic // one that will contain the new instruction in its first traced segment. TraceDumper::FunctionCallUP new_root = std::make_unique(cursor_sp, symbol_info); // This new root will own the previous root through an untraced prefix segment. new_root->SetUntracedPrefixSegment(std::move(roots.back())); roots.pop_back(); // We update the roots container to point to the new root roots.emplace_back(std::move(new_root)); return *roots.back(); } /// Append an instruction to a function call forest. The new instruction might /// be appended to the current segment, to a new nest call, or return to an /// ancestor call. /// /// \param[in] exe_ctx /// The exeuction context of the traced thread. /// /// \param[in] last_function_call /// The chronologically most recent function call before the new instruction. /// /// \param[in] prev_symbol_info /// The symbol information of the previous instruction in the trace. /// /// \param[in] symbol_info /// The symbol information of the new instruction. /// /// \param[in] cursor_sp /// The cursor pointing to the new instruction. /// /// \param[in,out] roots /// The object owning the roots. It might be modified if a new root needs to /// be created. /// /// \return /// A reference to the function call that owns the new instruction. static TraceDumper::FunctionCall &AppendInstructionToFunctionCallForest( const ExecutionContext &exe_ctx, TraceDumper::FunctionCall *last_function_call, const TraceDumper::SymbolInfo &prev_symbol_info, const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp, std::vector &roots) { if (!last_function_call || last_function_call->IsError()) { // We create a brand new root roots.emplace_back( std::make_unique(cursor_sp, symbol_info)); return *roots.back(); } lldb_private::AddressRange range; if (symbol_info.sc.GetAddressRange( eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol, 0, /*inline_block_range*/ true, range)) { if (range.GetBaseAddress() == symbol_info.address) { // Our instruction is the first instruction of a function. This has // to be a call. This should also identify if a trampoline or the linker // is making a call using a non-CALL instruction. return last_function_call->GetLastTracedSegment().CreateNestedCall( cursor_sp, symbol_info); } } if (IsSameInstructionSymbolContext(prev_symbol_info, symbol_info, /*check_source_line_info=*/false)) { // We are still in the same function. This can't be a call because otherwise // we would be in the first instruction of the symbol. last_function_call->GetLastTracedSegment().AppendInsn(cursor_sp, symbol_info); return *last_function_call; } // Now we are in a different symbol. Let's see if this is a return or a // call const InstructionSP &insn = last_function_call->GetLastTracedSegment() .GetLastInstructionSymbolInfo() .instruction; InstructionControlFlowKind insn_kind = insn ? insn->GetControlFlowKind(&exe_ctx) : eInstructionControlFlowKindOther; switch (insn_kind) { case lldb::eInstructionControlFlowKindCall: case lldb::eInstructionControlFlowKindFarCall: { // This is a regular call return last_function_call->GetLastTracedSegment().CreateNestedCall( cursor_sp, symbol_info); } case lldb::eInstructionControlFlowKindFarReturn: case lldb::eInstructionControlFlowKindReturn: { // We should have caught most trampolines and linker functions earlier, so // let's assume this is a regular return. return AppendReturnedInstructionToFunctionCallForest( *last_function_call, symbol_info, cursor_sp, roots); } default: // we changed symbols not using a call or return and we are not in the // beginning of a symbol, so this should be something very artificial // or maybe a jump to some label in the middle of it section. // We first check if it's a return from an inline method if (prev_symbol_info.sc.block && prev_symbol_info.sc.block->GetContainingInlinedBlock()) { return AppendReturnedInstructionToFunctionCallForest( *last_function_call, symbol_info, cursor_sp, roots); } // Now We assume it's a call. We should revisit this in the future. // Ideally we should be able to decide whether to create a new tree, // or go deeper or higher in the stack. return last_function_call->GetLastTracedSegment().CreateNestedCall( cursor_sp, symbol_info); } } /// Append an error to a function call forest. The new error might be appended /// to the current segment if it contains errors or will create a new root. /// /// \param[in] last_function_call /// The chronologically most recent function call before the new error. /// /// \param[in] cursor_sp /// The cursor pointing to the new error. /// /// \param[in,out] roots /// The object owning the roots. It might be modified if a new root needs to /// be created. /// /// \return /// A reference to the function call that owns the new error. TraceDumper::FunctionCall &AppendErrorToFunctionCallForest( TraceDumper::FunctionCall *last_function_call, TraceCursorSP &cursor_sp, std::vector &roots) { if (last_function_call && last_function_call->IsError()) { last_function_call->GetLastTracedSegment().AppendInsn( cursor_sp, TraceDumper::SymbolInfo{}); return *last_function_call; } else { roots.emplace_back(std::make_unique( cursor_sp, TraceDumper::SymbolInfo{})); return *roots.back(); } } static std::vector CreateFunctionCallForest(TraceCursorSP &cursor_sp, const ExecutionContext &exe_ctx) { std::vector roots; TraceDumper::SymbolInfo prev_symbol_info; TraceDumper::FunctionCall *last_function_call = nullptr; for (; cursor_sp->HasValue(); cursor_sp->Next()) { if (cursor_sp->IsError()) { last_function_call = &AppendErrorToFunctionCallForest(last_function_call, cursor_sp, roots); prev_symbol_info = {}; } else if (cursor_sp->IsInstruction()) { TraceDumper::SymbolInfo symbol_info = CalculateSymbolInfo( exe_ctx, cursor_sp->GetLoadAddress(), prev_symbol_info); last_function_call = &AppendInstructionToFunctionCallForest( exe_ctx, last_function_call, prev_symbol_info, symbol_info, cursor_sp, roots); prev_symbol_info = symbol_info; } else if (cursor_sp->GetEventType() == eTraceEventCPUChanged) { // TODO: In case of a CPU change, we create a new root because we haven't // investigated yet if a call tree can safely continue or if interrupts // could have polluted the original call tree. last_function_call = nullptr; prev_symbol_info = {}; } } return roots; } void TraceDumper::DumpFunctionCalls() { ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP(); ExecutionContext exe_ctx; thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx); m_writer_up->FunctionCallForest( CreateFunctionCallForest(m_cursor_sp, exe_ctx)); }