915 lines · cpp
1//===-- TraceDumper.cpp ---------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Target/TraceDumper.h"10#include "lldb/Core/Module.h"11#include "lldb/Symbol/CompileUnit.h"12#include "lldb/Symbol/Function.h"13#include "lldb/Target/ExecutionContext.h"14#include "lldb/Target/Process.h"15#include "lldb/Target/SectionLoadList.h"16#include <optional>17 18using namespace lldb;19using namespace lldb_private;20using namespace llvm;21 22/// \return23/// The given string or \b std::nullopt if it's empty.24static std::optional<const char *> ToOptionalString(const char *s) {25 if (!s)26 return std::nullopt;27 return s;28}29 30static const char *GetModuleName(const SymbolContext &sc) {31 if (!sc.module_sp)32 return nullptr;33 return sc.module_sp->GetFileSpec().GetFilename().AsCString();34}35 36/// \return37/// The module name (basename if the module is a file, or the actual name if38/// it's a virtual module), or \b nullptr if no name nor module was found.39static const char *GetModuleName(const TraceDumper::TraceItem &item) {40 if (!item.symbol_info)41 return nullptr;42 return GetModuleName(item.symbol_info->sc);43}44 45// This custom LineEntry validator is neded because some line_entries have46// 0 as line, which is meaningless. Notice that LineEntry::IsValid only47// checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX.48static bool IsLineEntryValid(const LineEntry &line_entry) {49 return line_entry.IsValid() && line_entry.line > 0;50}51 52/// \return53/// \b true if the provided line entries match line, column and source file.54/// This function assumes that the line entries are valid.55static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {56 if (a.line != b.line)57 return false;58 if (a.column != b.column)59 return false;60 return a.GetFile() == b.GetFile();61}62 63/// Compare the symbol contexts of the provided \a SymbolInfo64/// objects.65///66/// \return67/// \a true if both instructions belong to the same scope level analized68/// in the following order:69/// - module70/// - symbol71/// - function72/// - inlined function73/// - source line info74static bool75IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo &prev_insn,76 const TraceDumper::SymbolInfo &insn,77 bool check_source_line_info = true) {78 // module checks79 if (insn.sc.module_sp != prev_insn.sc.module_sp)80 return false;81 82 // symbol checks83 if (insn.sc.symbol != prev_insn.sc.symbol)84 return false;85 86 // function checks87 if (!insn.sc.function && !prev_insn.sc.function)88 return true; // This means two dangling instruction in the same module. We89 // can assume they are part of the same unnamed symbol90 else if (insn.sc.function != prev_insn.sc.function)91 return false;92 93 Block *inline_block_a =94 insn.sc.block ? insn.sc.block->GetContainingInlinedBlock() : nullptr;95 Block *inline_block_b = prev_insn.sc.block96 ? prev_insn.sc.block->GetContainingInlinedBlock()97 : nullptr;98 if (inline_block_a != inline_block_b)99 return false;100 101 // line entry checks102 if (!check_source_line_info)103 return true;104 105 const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry);106 const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry);107 if (curr_line_valid && prev_line_valid)108 return FileLineAndColumnMatches(insn.sc.line_entry,109 prev_insn.sc.line_entry);110 return curr_line_valid == prev_line_valid;111}112 113class OutputWriterCLI : public TraceDumper::OutputWriter {114public:115 OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread)116 : m_s(s), m_options(options) {117 m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID());118 };119 120 void NoMoreData() override { m_s << " no more data\n"; }121 122 void FunctionCallForest(123 const std::vector<TraceDumper::FunctionCallUP> &forest) override {124 for (size_t i = 0; i < forest.size(); i++) {125 m_s.Format("\n[call tree #{0}]\n", i);126 DumpFunctionCallTree(*forest[i]);127 }128 }129 130 void TraceItem(const TraceDumper::TraceItem &item) override {131 if (item.symbol_info) {132 if (!item.prev_symbol_info ||133 !IsSameInstructionSymbolContext(*item.prev_symbol_info,134 *item.symbol_info)) {135 m_s << " ";136 const char *module_name = GetModuleName(item);137 if (!module_name)138 m_s << "(none)";139 else if (!item.symbol_info->sc.function && !item.symbol_info->sc.symbol)140 m_s.Format("{0}`(none)", module_name);141 else142 item.symbol_info->sc.DumpStopContext(143 &m_s, item.symbol_info->exe_ctx.GetTargetPtr(),144 item.symbol_info->address,145 /*show_fullpaths=*/false,146 /*show_module=*/true, /*show_inlined_frames=*/false,147 /*show_function_arguments=*/true,148 /*show_function_name=*/true);149 m_s << "\n";150 }151 }152 153 if (item.error && !m_was_prev_instruction_an_error)154 m_s << " ...missing instructions\n";155 156 m_s.Format(" {0}: ", item.id);157 158 if (m_options.show_timestamps) {159 m_s.Format("[{0}] ", item.timestamp160 ? formatv("{0:3} ns", *item.timestamp).str()161 : "unavailable");162 }163 164 if (item.event) {165 m_s << "(event) " << TraceCursor::EventKindToString(*item.event);166 switch (*item.event) {167 case eTraceEventCPUChanged:168 m_s.Format(" [new CPU={0}]",169 item.cpu_id ? std::to_string(*item.cpu_id) : "unavailable");170 break;171 case eTraceEventHWClockTick:172 m_s.Format(" [{0}]", item.hw_clock ? std::to_string(*item.hw_clock)173 : "unavailable");174 break;175 case eTraceEventDisabledHW:176 case eTraceEventDisabledSW:177 break;178 case eTraceEventSyncPoint:179 m_s.Format(" [{0}]", item.sync_point_metadata);180 break;181 }182 } else if (item.error) {183 m_s << "(error) " << *item.error;184 } else {185 m_s.Format("{0:x+16}", item.load_address);186 if (item.symbol_info && item.symbol_info->instruction) {187 m_s << " ";188 item.symbol_info->instruction->Dump(189 &m_s, /*max_opcode_byte_size=*/0,190 /*show_address=*/false,191 /*show_bytes=*/false, m_options.show_control_flow_kind,192 &item.symbol_info->exe_ctx, &item.symbol_info->sc,193 /*prev_sym_ctx=*/nullptr,194 /*disassembly_addr_format=*/nullptr,195 /*max_address_text_size=*/0);196 }197 }198 199 m_was_prev_instruction_an_error = (bool)item.error;200 m_s << "\n";201 }202 203private:204 void205 DumpSegmentContext(const TraceDumper::FunctionCall::TracedSegment &segment) {206 if (segment.GetOwningCall().IsError()) {207 m_s << "<tracing errors>";208 return;209 }210 211 const SymbolContext &first_sc = segment.GetFirstInstructionSymbolInfo().sc;212 first_sc.DumpStopContext(213 &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(),214 segment.GetFirstInstructionSymbolInfo().address,215 /*show_fullpaths=*/false,216 /*show_module=*/true, /*show_inlined_frames=*/false,217 /*show_function_arguments=*/true,218 /*show_function_name=*/true);219 m_s << " to ";220 const SymbolContext &last_sc = segment.GetLastInstructionSymbolInfo().sc;221 if (IsLineEntryValid(first_sc.line_entry) &&222 IsLineEntryValid(last_sc.line_entry)) {223 m_s.Format("{0}:{1}", last_sc.line_entry.line, last_sc.line_entry.column);224 } else {225 last_sc.DumpStopContext(226 &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(),227 segment.GetLastInstructionSymbolInfo().address,228 /*show_fullpaths=*/false,229 /*show_module=*/false, /*show_inlined_frames=*/false,230 /*show_function_arguments=*/false,231 /*show_function_name=*/false);232 }233 }234 235 void DumpUntracedContext(const TraceDumper::FunctionCall &function_call) {236 if (function_call.IsError()) {237 m_s << "tracing error";238 }239 const SymbolContext &sc = function_call.GetSymbolInfo().sc;240 241 const char *module_name = GetModuleName(sc);242 if (!module_name)243 m_s << "(none)";244 else if (!sc.function && !sc.symbol)245 m_s << module_name << "`(none)";246 else247 m_s << module_name << "`" << sc.GetFunctionName().AsCString();248 }249 250 void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) {251 if (function_call.GetUntracedPrefixSegment()) {252 m_s.Indent();253 DumpUntracedContext(function_call);254 m_s << "\n";255 256 m_s.IndentMore();257 DumpFunctionCallTree(function_call.GetUntracedPrefixSegment()->GetNestedCall());258 m_s.IndentLess();259 }260 261 for (const TraceDumper::FunctionCall::TracedSegment &segment :262 function_call.GetTracedSegments()) {263 m_s.Indent();264 DumpSegmentContext(segment);265 m_s.Format(" [{0}, {1}]\n", segment.GetFirstInstructionID(),266 segment.GetLastInstructionID());267 268 segment.IfNestedCall([&](const TraceDumper::FunctionCall &nested_call) {269 m_s.IndentMore();270 DumpFunctionCallTree(nested_call);271 m_s.IndentLess();272 });273 }274 }275 276 Stream &m_s;277 TraceDumperOptions m_options;278 bool m_was_prev_instruction_an_error = false;279};280 281class OutputWriterJSON : public TraceDumper::OutputWriter {282 /* schema:283 error_message: string284 | {285 "event": string,286 "id": decimal,287 "tsc"?: string decimal,288 "cpuId"? decimal,289 } | {290 "error": string,291 "id": decimal,292 "tsc"?: string decimal,293 | {294 "loadAddress": string decimal,295 "id": decimal,296 "hwClock"?: string decimal,297 "syncPointMetadata"?: string,298 "timestamp_ns"?: string decimal,299 "module"?: string,300 "symbol"?: string,301 "line"?: decimal,302 "column"?: decimal,303 "source"?: string,304 "mnemonic"?: string,305 "controlFlowKind"?: string,306 }307 */308public:309 OutputWriterJSON(Stream &s, const TraceDumperOptions &options)310 : m_s(s), m_options(options),311 m_j(m_s.AsRawOstream(),312 /*IndentSize=*/options.pretty_print_json ? 2 : 0) {313 m_j.arrayBegin();314 };315 316 ~OutputWriterJSON() { m_j.arrayEnd(); }317 318 void FunctionCallForest(319 const std::vector<TraceDumper::FunctionCallUP> &forest) override {320 for (size_t i = 0; i < forest.size(); i++) {321 m_j.object([&] { DumpFunctionCallTree(*forest[i]); });322 }323 }324 325 void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) {326 if (function_call.GetUntracedPrefixSegment()) {327 m_j.attributeObject("untracedPrefixSegment", [&] {328 m_j.attributeObject("nestedCall", [&] {329 DumpFunctionCallTree(330 function_call.GetUntracedPrefixSegment()->GetNestedCall());331 });332 });333 }334 335 if (!function_call.GetTracedSegments().empty()) {336 m_j.attributeArray("tracedSegments", [&] {337 for (const TraceDumper::FunctionCall::TracedSegment &segment :338 function_call.GetTracedSegments()) {339 m_j.object([&] {340 m_j.attribute("firstInstructionId",341 std::to_string(segment.GetFirstInstructionID()));342 m_j.attribute("lastInstructionId",343 std::to_string(segment.GetLastInstructionID()));344 segment.IfNestedCall(345 [&](const TraceDumper::FunctionCall &nested_call) {346 m_j.attributeObject(347 "nestedCall", [&] { DumpFunctionCallTree(nested_call); });348 });349 });350 }351 });352 }353 }354 355 void DumpEvent(const TraceDumper::TraceItem &item) {356 m_j.attribute("event", TraceCursor::EventKindToString(*item.event));357 switch (*item.event) {358 case eTraceEventCPUChanged:359 m_j.attribute("cpuId", item.cpu_id);360 break;361 case eTraceEventHWClockTick:362 m_j.attribute("hwClock", item.hw_clock);363 break;364 case eTraceEventDisabledHW:365 case eTraceEventDisabledSW:366 break;367 case eTraceEventSyncPoint:368 m_j.attribute("syncPointMetadata", item.sync_point_metadata);369 break;370 }371 }372 373 void DumpInstruction(const TraceDumper::TraceItem &item) {374 m_j.attribute("loadAddress", formatv("{0:x}", item.load_address));375 if (item.symbol_info) {376 m_j.attribute("module", ToOptionalString(GetModuleName(item)));377 m_j.attribute(378 "symbol",379 ToOptionalString(item.symbol_info->sc.GetFunctionName().AsCString()));380 381 if (lldb::InstructionSP instruction = item.symbol_info->instruction) {382 ExecutionContext exe_ctx = item.symbol_info->exe_ctx;383 m_j.attribute("mnemonic",384 ToOptionalString(instruction->GetMnemonic(&exe_ctx)));385 if (m_options.show_control_flow_kind) {386 lldb::InstructionControlFlowKind instruction_control_flow_kind =387 instruction->GetControlFlowKind(&exe_ctx);388 m_j.attribute("controlFlowKind",389 ToOptionalString(390 Instruction::GetNameForInstructionControlFlowKind(391 instruction_control_flow_kind)));392 }393 }394 395 if (IsLineEntryValid(item.symbol_info->sc.line_entry)) {396 m_j.attribute(397 "source",398 ToOptionalString(399 item.symbol_info->sc.line_entry.GetFile().GetPath().c_str()));400 m_j.attribute("line", item.symbol_info->sc.line_entry.line);401 m_j.attribute("column", item.symbol_info->sc.line_entry.column);402 }403 }404 }405 406 void TraceItem(const TraceDumper::TraceItem &item) override {407 m_j.object([&] {408 m_j.attribute("id", item.id);409 if (m_options.show_timestamps)410 m_j.attribute("timestamp_ns", item.timestamp411 ? std::optional<std::string>(412 std::to_string(*item.timestamp))413 : std::nullopt);414 415 if (item.event) {416 DumpEvent(item);417 } else if (item.error) {418 m_j.attribute("error", *item.error);419 } else {420 DumpInstruction(item);421 }422 });423 }424 425private:426 Stream &m_s;427 TraceDumperOptions m_options;428 json::OStream m_j;429};430 431static std::unique_ptr<TraceDumper::OutputWriter>432CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) {433 if (options.json)434 return std::unique_ptr<TraceDumper::OutputWriter>(435 new OutputWriterJSON(s, options));436 else437 return std::unique_ptr<TraceDumper::OutputWriter>(438 new OutputWriterCLI(s, options, thread));439}440 441TraceDumper::TraceDumper(lldb::TraceCursorSP cursor_sp, Stream &s,442 const TraceDumperOptions &options)443 : m_cursor_sp(std::move(cursor_sp)), m_options(options),444 m_writer_up(CreateWriter(445 s, m_options, *m_cursor_sp->GetExecutionContextRef().GetThreadSP())) {446 447 if (m_options.id)448 m_cursor_sp->GoToId(*m_options.id);449 else if (m_options.forwards)450 m_cursor_sp->Seek(0, lldb::eTraceCursorSeekTypeBeginning);451 else452 m_cursor_sp->Seek(0, lldb::eTraceCursorSeekTypeEnd);453 454 m_cursor_sp->SetForwards(m_options.forwards);455 if (m_options.skip) {456 m_cursor_sp->Seek((m_options.forwards ? 1 : -1) * *m_options.skip,457 lldb::eTraceCursorSeekTypeCurrent);458 }459}460 461TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() {462 TraceItem item = {};463 item.id = m_cursor_sp->GetId();464 465 if (m_options.show_timestamps)466 item.timestamp = m_cursor_sp->GetWallClockTime();467 return item;468}469 470/// Find the symbol context for the given address reusing the previous471/// instruction's symbol context when possible.472static SymbolContext473CalculateSymbolContext(const Address &address,474 const SymbolContext &prev_symbol_context) {475 lldb_private::AddressRange range;476 if (prev_symbol_context.GetAddressRange(eSymbolContextEverything, 0,477 /*inline_block_range*/ true, range) &&478 range.Contains(address))479 return prev_symbol_context;480 481 SymbolContext sc;482 address.CalculateSymbolContext(&sc, eSymbolContextEverything);483 return sc;484}485 486/// Find the disassembler for the given address reusing the previous487/// instruction's disassembler when possible.488static std::tuple<DisassemblerSP, InstructionSP>489CalculateDisass(const TraceDumper::SymbolInfo &symbol_info,490 const TraceDumper::SymbolInfo &prev_symbol_info,491 const ExecutionContext &exe_ctx) {492 if (prev_symbol_info.disassembler) {493 if (InstructionSP instruction =494 prev_symbol_info.disassembler->GetInstructionList()495 .GetInstructionAtAddress(symbol_info.address))496 return std::make_tuple(prev_symbol_info.disassembler, instruction);497 }498 499 if (symbol_info.sc.function) {500 if (DisassemblerSP disassembler =501 symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) {502 if (InstructionSP instruction =503 disassembler->GetInstructionList().GetInstructionAtAddress(504 symbol_info.address))505 return std::make_tuple(disassembler, instruction);506 }507 }508 // We fallback to a single instruction disassembler509 Target &target = exe_ctx.GetTargetRef();510 const ArchSpec arch = target.GetArchitecture();511 lldb_private::AddressRange range(symbol_info.address,512 arch.GetMaximumOpcodeByteSize());513 DisassemblerSP disassembler = Disassembler::DisassembleRange(514 arch, /*plugin_name=*/nullptr,515 /*flavor=*/nullptr, /*cpu=*/nullptr, /*features=*/nullptr, target, range);516 return std::make_tuple(517 disassembler,518 disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress(519 symbol_info.address)520 : InstructionSP());521}522 523static TraceDumper::SymbolInfo524CalculateSymbolInfo(const ExecutionContext &exe_ctx, lldb::addr_t load_address,525 const TraceDumper::SymbolInfo &prev_symbol_info) {526 TraceDumper::SymbolInfo symbol_info;527 symbol_info.exe_ctx = exe_ctx;528 symbol_info.address.SetLoadAddress(load_address, exe_ctx.GetTargetPtr());529 symbol_info.sc =530 CalculateSymbolContext(symbol_info.address, prev_symbol_info.sc);531 std::tie(symbol_info.disassembler, symbol_info.instruction) =532 CalculateDisass(symbol_info, prev_symbol_info, exe_ctx);533 return symbol_info;534}535 536std::optional<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) {537 ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP();538 539 SymbolInfo prev_symbol_info;540 std::optional<lldb::user_id_t> last_id;541 542 ExecutionContext exe_ctx;543 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);544 545 for (size_t insn_seen = 0; insn_seen < count && m_cursor_sp->HasValue();546 m_cursor_sp->Next()) {547 548 last_id = m_cursor_sp->GetId();549 TraceItem item = CreatRawTraceItem();550 551 if (m_cursor_sp->IsEvent() && m_options.show_events) {552 item.event = m_cursor_sp->GetEventType();553 switch (*item.event) {554 case eTraceEventCPUChanged:555 item.cpu_id = m_cursor_sp->GetCPU();556 break;557 case eTraceEventHWClockTick:558 item.hw_clock = m_cursor_sp->GetHWClock();559 break;560 case eTraceEventDisabledHW:561 case eTraceEventDisabledSW:562 break;563 case eTraceEventSyncPoint:564 item.sync_point_metadata = m_cursor_sp->GetSyncPointMetadata();565 break;566 }567 m_writer_up->TraceItem(item);568 } else if (m_cursor_sp->IsError()) {569 item.error = m_cursor_sp->GetError();570 m_writer_up->TraceItem(item);571 } else if (m_cursor_sp->IsInstruction() && !m_options.only_events) {572 insn_seen++;573 item.load_address = m_cursor_sp->GetLoadAddress();574 575 if (!m_options.raw) {576 SymbolInfo symbol_info =577 CalculateSymbolInfo(exe_ctx, item.load_address, prev_symbol_info);578 item.prev_symbol_info = prev_symbol_info;579 item.symbol_info = symbol_info;580 prev_symbol_info = symbol_info;581 }582 m_writer_up->TraceItem(item);583 }584 }585 if (!m_cursor_sp->HasValue())586 m_writer_up->NoMoreData();587 return last_id;588}589 590void TraceDumper::FunctionCall::TracedSegment::AppendInsn(591 const TraceCursorSP &cursor_sp,592 const TraceDumper::SymbolInfo &symbol_info) {593 m_last_insn_id = cursor_sp->GetId();594 m_last_symbol_info = symbol_info;595}596 597lldb::user_id_t598TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const {599 return m_first_insn_id;600}601 602lldb::user_id_t603TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const {604 return m_last_insn_id;605}606 607void TraceDumper::FunctionCall::TracedSegment::IfNestedCall(608 std::function<void(const FunctionCall &function_call)> callback) const {609 if (m_nested_call)610 callback(*m_nested_call);611}612 613const TraceDumper::FunctionCall &614TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const {615 return m_owning_call;616}617 618TraceDumper::FunctionCall &619TraceDumper::FunctionCall::TracedSegment::CreateNestedCall(620 const TraceCursorSP &cursor_sp,621 const TraceDumper::SymbolInfo &symbol_info) {622 m_nested_call = std::make_unique<FunctionCall>(cursor_sp, symbol_info);623 m_nested_call->SetParentCall(m_owning_call);624 return *m_nested_call;625}626 627const TraceDumper::SymbolInfo &628TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo()629 const {630 return m_first_symbol_info;631}632 633const TraceDumper::SymbolInfo &634TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const {635 return m_last_symbol_info;636}637 638const TraceDumper::FunctionCall &639TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const {640 return *m_nested_call;641}642 643TraceDumper::FunctionCall::FunctionCall(644 const TraceCursorSP &cursor_sp,645 const TraceDumper::SymbolInfo &symbol_info) {646 m_is_error = cursor_sp->IsError();647 AppendSegment(cursor_sp, symbol_info);648}649 650void TraceDumper::FunctionCall::AppendSegment(651 const TraceCursorSP &cursor_sp,652 const TraceDumper::SymbolInfo &symbol_info) {653 m_traced_segments.emplace_back(cursor_sp, symbol_info, *this);654}655 656const TraceDumper::SymbolInfo &657TraceDumper::FunctionCall::GetSymbolInfo() const {658 return m_traced_segments.back().GetLastInstructionSymbolInfo();659}660 661bool TraceDumper::FunctionCall::IsError() const { return m_is_error; }662 663const std::deque<TraceDumper::FunctionCall::TracedSegment> &664TraceDumper::FunctionCall::GetTracedSegments() const {665 return m_traced_segments;666}667 668TraceDumper::FunctionCall::TracedSegment &669TraceDumper::FunctionCall::GetLastTracedSegment() {670 return m_traced_segments.back();671}672 673const std::optional<TraceDumper::FunctionCall::UntracedPrefixSegment> &674TraceDumper::FunctionCall::GetUntracedPrefixSegment() const {675 return m_untraced_prefix_segment;676}677 678void TraceDumper::FunctionCall::SetUntracedPrefixSegment(679 TraceDumper::FunctionCallUP &&nested_call) {680 m_untraced_prefix_segment.emplace(std::move(nested_call));681}682 683TraceDumper::FunctionCall *TraceDumper::FunctionCall::GetParentCall() const {684 return m_parent_call;685}686 687void TraceDumper::FunctionCall::SetParentCall(688 TraceDumper::FunctionCall &parent_call) {689 m_parent_call = &parent_call;690}691 692/// Given an instruction that happens after a return, find the ancestor function693/// call that owns it. If this ancestor doesn't exist, create a new ancestor and694/// make it the root of the tree.695///696/// \param[in] last_function_call697/// The function call that performs the return.698///699/// \param[in] symbol_info700/// The symbol information of the instruction after the return.701///702/// \param[in] cursor_sp703/// The cursor pointing to the instruction after the return.704///705/// \param[in,out] roots706/// The object owning the roots. It might be modified if a new root needs to707/// be created.708///709/// \return710/// A reference to the function call that owns the new instruction711static TraceDumper::FunctionCall &AppendReturnedInstructionToFunctionCallForest(712 TraceDumper::FunctionCall &last_function_call,713 const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp,714 std::vector<TraceDumper::FunctionCallUP> &roots) {715 716 // We omit the current node because we can't return to itself.717 TraceDumper::FunctionCall *ancestor = last_function_call.GetParentCall();718 719 for (; ancestor; ancestor = ancestor->GetParentCall()) {720 // This loop traverses the tree until it finds a call that we can return to.721 if (IsSameInstructionSymbolContext(ancestor->GetSymbolInfo(), symbol_info,722 /*check_source_line_info=*/false)) {723 // We returned to this symbol, so we are assuming we are returning there724 // Note: If this is not robust enough, we should actually check if we725 // returning to the instruction that follows the last instruction from726 // that call, as that's the behavior of CALL instructions.727 ancestor->AppendSegment(cursor_sp, symbol_info);728 return *ancestor;729 }730 }731 732 // We didn't find the call we were looking for, so we now create a synthetic733 // one that will contain the new instruction in its first traced segment.734 TraceDumper::FunctionCallUP new_root =735 std::make_unique<TraceDumper::FunctionCall>(cursor_sp, symbol_info);736 // This new root will own the previous root through an untraced prefix segment.737 new_root->SetUntracedPrefixSegment(std::move(roots.back()));738 roots.pop_back();739 // We update the roots container to point to the new root740 roots.emplace_back(std::move(new_root));741 return *roots.back();742}743 744/// Append an instruction to a function call forest. The new instruction might745/// be appended to the current segment, to a new nest call, or return to an746/// ancestor call.747///748/// \param[in] exe_ctx749/// The exeuction context of the traced thread.750///751/// \param[in] last_function_call752/// The chronologically most recent function call before the new instruction.753///754/// \param[in] prev_symbol_info755/// The symbol information of the previous instruction in the trace.756///757/// \param[in] symbol_info758/// The symbol information of the new instruction.759///760/// \param[in] cursor_sp761/// The cursor pointing to the new instruction.762///763/// \param[in,out] roots764/// The object owning the roots. It might be modified if a new root needs to765/// be created.766///767/// \return768/// A reference to the function call that owns the new instruction.769static TraceDumper::FunctionCall &AppendInstructionToFunctionCallForest(770 const ExecutionContext &exe_ctx,771 TraceDumper::FunctionCall *last_function_call,772 const TraceDumper::SymbolInfo &prev_symbol_info,773 const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp,774 std::vector<TraceDumper::FunctionCallUP> &roots) {775 if (!last_function_call || last_function_call->IsError()) {776 // We create a brand new root777 roots.emplace_back(778 std::make_unique<TraceDumper::FunctionCall>(cursor_sp, symbol_info));779 return *roots.back();780 }781 782 lldb_private::AddressRange range;783 if (symbol_info.sc.GetAddressRange(784 eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol,785 0, /*inline_block_range*/ true, range)) {786 if (range.GetBaseAddress() == symbol_info.address) {787 // Our instruction is the first instruction of a function. This has788 // to be a call. This should also identify if a trampoline or the linker789 // is making a call using a non-CALL instruction.790 return last_function_call->GetLastTracedSegment().CreateNestedCall(791 cursor_sp, symbol_info);792 }793 }794 if (IsSameInstructionSymbolContext(prev_symbol_info, symbol_info,795 /*check_source_line_info=*/false)) {796 // We are still in the same function. This can't be a call because otherwise797 // we would be in the first instruction of the symbol.798 last_function_call->GetLastTracedSegment().AppendInsn(cursor_sp,799 symbol_info);800 return *last_function_call;801 }802 // Now we are in a different symbol. Let's see if this is a return or a803 // call804 const InstructionSP &insn = last_function_call->GetLastTracedSegment()805 .GetLastInstructionSymbolInfo()806 .instruction;807 InstructionControlFlowKind insn_kind =808 insn ? insn->GetControlFlowKind(&exe_ctx)809 : eInstructionControlFlowKindOther;810 811 switch (insn_kind) {812 case lldb::eInstructionControlFlowKindCall:813 case lldb::eInstructionControlFlowKindFarCall: {814 // This is a regular call815 return last_function_call->GetLastTracedSegment().CreateNestedCall(816 cursor_sp, symbol_info);817 }818 case lldb::eInstructionControlFlowKindFarReturn:819 case lldb::eInstructionControlFlowKindReturn: {820 // We should have caught most trampolines and linker functions earlier, so821 // let's assume this is a regular return.822 return AppendReturnedInstructionToFunctionCallForest(823 *last_function_call, symbol_info, cursor_sp, roots);824 }825 default:826 // we changed symbols not using a call or return and we are not in the827 // beginning of a symbol, so this should be something very artificial828 // or maybe a jump to some label in the middle of it section.829 830 // We first check if it's a return from an inline method831 if (prev_symbol_info.sc.block &&832 prev_symbol_info.sc.block->GetContainingInlinedBlock()) {833 return AppendReturnedInstructionToFunctionCallForest(834 *last_function_call, symbol_info, cursor_sp, roots);835 }836 // Now We assume it's a call. We should revisit this in the future.837 // Ideally we should be able to decide whether to create a new tree,838 // or go deeper or higher in the stack.839 return last_function_call->GetLastTracedSegment().CreateNestedCall(840 cursor_sp, symbol_info);841 }842}843 844/// Append an error to a function call forest. The new error might be appended845/// to the current segment if it contains errors or will create a new root.846///847/// \param[in] last_function_call848/// The chronologically most recent function call before the new error.849///850/// \param[in] cursor_sp851/// The cursor pointing to the new error.852///853/// \param[in,out] roots854/// The object owning the roots. It might be modified if a new root needs to855/// be created.856///857/// \return858/// A reference to the function call that owns the new error.859TraceDumper::FunctionCall &AppendErrorToFunctionCallForest(860 TraceDumper::FunctionCall *last_function_call, TraceCursorSP &cursor_sp,861 std::vector<TraceDumper::FunctionCallUP> &roots) {862 if (last_function_call && last_function_call->IsError()) {863 last_function_call->GetLastTracedSegment().AppendInsn(864 cursor_sp, TraceDumper::SymbolInfo{});865 return *last_function_call;866 } else {867 roots.emplace_back(std::make_unique<TraceDumper::FunctionCall>(868 cursor_sp, TraceDumper::SymbolInfo{}));869 return *roots.back();870 }871}872 873static std::vector<TraceDumper::FunctionCallUP>874CreateFunctionCallForest(TraceCursorSP &cursor_sp,875 const ExecutionContext &exe_ctx) {876 877 std::vector<TraceDumper::FunctionCallUP> roots;878 TraceDumper::SymbolInfo prev_symbol_info;879 880 TraceDumper::FunctionCall *last_function_call = nullptr;881 882 for (; cursor_sp->HasValue(); cursor_sp->Next()) {883 if (cursor_sp->IsError()) {884 last_function_call = &AppendErrorToFunctionCallForest(last_function_call,885 cursor_sp, roots);886 prev_symbol_info = {};887 } else if (cursor_sp->IsInstruction()) {888 TraceDumper::SymbolInfo symbol_info = CalculateSymbolInfo(889 exe_ctx, cursor_sp->GetLoadAddress(), prev_symbol_info);890 891 last_function_call = &AppendInstructionToFunctionCallForest(892 exe_ctx, last_function_call, prev_symbol_info, symbol_info, cursor_sp,893 roots);894 prev_symbol_info = symbol_info;895 } else if (cursor_sp->GetEventType() == eTraceEventCPUChanged) {896 // TODO: In case of a CPU change, we create a new root because we haven't897 // investigated yet if a call tree can safely continue or if interrupts898 // could have polluted the original call tree.899 last_function_call = nullptr;900 prev_symbol_info = {};901 }902 }903 904 return roots;905}906 907void TraceDumper::DumpFunctionCalls() {908 ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP();909 ExecutionContext exe_ctx;910 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);911 912 m_writer_up->FunctionCallForest(913 CreateFunctionCallForest(m_cursor_sp, exe_ctx));914}915