diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Target/TraceDumper.cpp')
-rw-r--r-- | contrib/llvm-project/lldb/source/Target/TraceDumper.cpp | 914 |
1 files changed, 914 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp b/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp new file mode 100644 index 000000000000..4ef8efc1a676 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp @@ -0,0 +1,914 @@ +//===-- 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 <optional> + +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<const char *> 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<TraceDumper::FunctionCallUP> &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 << "<tracing errors>"; + 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<TraceDumper::FunctionCallUP> &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::string>( + 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<TraceDumper::OutputWriter> +CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) { + if (options.json) + return std::unique_ptr<TraceDumper::OutputWriter>( + new OutputWriterJSON(s, options)); + else + return std::unique_ptr<TraceDumper::OutputWriter>( + 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<DisassemblerSP, InstructionSP> +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<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) { + ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP(); + + SymbolInfo prev_symbol_info; + std::optional<lldb::user_id_t> 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<void(const FunctionCall &function_call)> 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<FunctionCall>(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::TracedSegment> & +TraceDumper::FunctionCall::GetTracedSegments() const { + return m_traced_segments; +} + +TraceDumper::FunctionCall::TracedSegment & +TraceDumper::FunctionCall::GetLastTracedSegment() { + return m_traced_segments.back(); +} + +const std::optional<TraceDumper::FunctionCall::UntracedPrefixSegment> & +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<TraceDumper::FunctionCallUP> &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<TraceDumper::FunctionCall>(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<TraceDumper::FunctionCallUP> &roots) { + if (!last_function_call || last_function_call->IsError()) { + // We create a brand new root + roots.emplace_back( + std::make_unique<TraceDumper::FunctionCall>(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<TraceDumper::FunctionCallUP> &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<TraceDumper::FunctionCall>( + cursor_sp, TraceDumper::SymbolInfo{})); + return *roots.back(); + } +} + +static std::vector<TraceDumper::FunctionCallUP> +CreateFunctionCallForest(TraceCursorSP &cursor_sp, + const ExecutionContext &exe_ctx) { + + std::vector<TraceDumper::FunctionCallUP> 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)); +} |