mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 22:16:05 +00:00

Prior to this patch, each core file plugin (ObjectFileMachO.cpp and ObjectFileMinindump.cpp) would calculate the address ranges to save in different ways. This patch adds a new function to Process.h/.cpp: ``` Status Process::CalculateCoreFileSaveRanges(lldb::SaveCoreStyle core_style, CoreFileMemoryRanges &ranges); ``` The patch updates the ObjectFileMachO::SaveCore(...) and ObjectFileMinindump::SaveCore(...) to use same code. This will allow core files to be consistent with the lldb::SaveCoreStyle across different core file creators and will allow us to add new core file saving features that do more complex things in future patches.
915 lines
32 KiB
C++
915 lines
32 KiB
C++
//===-- 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.file == b.file;
|
|
}
|
|
|
|
/// 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.file.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));
|
|
}
|