aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/lldb/source/Target/TraceDumper.cpp')
-rw-r--r--contrib/llvm-project/lldb/source/Target/TraceDumper.cpp914
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));
+}