diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -14,6 +14,7 @@ #include "llvm/Support/JSON.h" #include "lldb/Core/PluginInterface.h" +#include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/TraceGDBRemotePackets.h" @@ -49,6 +50,12 @@ Backwards, }; + enum class TraceDumpFormat { + Default = 0, + Raw = 1, + JSON = 2, + }; + /// Dump the trace data that this plug-in has access to. /// /// This function will dump all of the trace data for all threads in a user @@ -167,11 +174,12 @@ /// \param[in] end_position /// The position of the last instruction to print. /// - /// \param[in] raw - /// Dump only instruction addresses without disassembly nor symbol - /// information. + /// \param[in] format + /// If \b Default, then dump instruction addresses with disassembly and + /// symbol information. If \b Raw, then only dump instruction addresses. + /// If \b JSON, then print addresses and disassembly in JSON format. void DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t end_position, bool raw); + size_t end_position, TraceDumpFormat format); /// Run the provided callback on the instructions of the trace of the given /// thread. @@ -204,14 +212,33 @@ std::function load_addr)> callback) = 0; + struct InstructionSymbolInfo { + SymbolContext sc; + Address address; + lldb::addr_t load_address; + lldb::DisassemblerSP disassembler; + lldb::InstructionSP instruction; + lldb_private::ExecutionContext exe_ctx; + }; + + /// Similar to \a TraverseInstructions byt the callback receives a \a + /// InstructionSymbolInfo object with symbol information for the given + /// instruction, calculated efficiently. + void TraverseInstructionsWithSymbolInfo( + Thread &thread, size_t position, TraceDirection direction, + std::function insn)> + callback); + /// Get the number of available instructions in the trace of the given thread. /// /// \param[in] thread /// The thread whose trace will be inspected. /// /// \return - /// The total number of instructions in the trace. - virtual size_t GetInstructionCount(Thread &thread) = 0; + /// The total number of instructions in the trace, or \a llvm::None if the + /// thread is not being traced. + virtual llvm::Optional GetInstructionCount(Thread &thread) = 0; /// Check if a thread is currently traced by this object. /// diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -2148,8 +2148,10 @@ if (position < 0) result.SetError("error: no more data"); else - trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(), - count, position, m_options.m_raw); + trace_sp->DumpTraceInstructions( + *thread_sp, result.GetOutputStream(), count, position, + m_options.m_raw ? Trace::TraceDumpFormat::Raw + : Trace::TraceDumpFormat::Default); return true; } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -70,7 +70,7 @@ std::function load_addr)> callback) override; - size_t GetInstructionCount(Thread &thread) override; + llvm::Optional GetInstructionCount(Thread &thread) override; size_t GetCursorPosition(Thread &thread) override; diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -122,11 +122,11 @@ break; } -size_t TraceIntelPT::GetInstructionCount(Thread &thread) { +Optional TraceIntelPT::GetInstructionCount(Thread &thread) { if (const DecodedThread *decoded_thread = Decode(thread)) return decoded_thread->GetInstructions().size(); else - return 0; + return None; } Expected TraceIntelPT::GetCPUInfoForLiveProcess() { diff --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp --- a/lldb/source/Target/Trace.cpp +++ b/lldb/source/Target/Trace.cpp @@ -101,6 +101,35 @@ return num == 0 ? 1 : static_cast(log10(num)) + 1; } +static bool +IsSameInstructionSymbolContext(Optional prev_insn, + Trace::InstructionSymbolInfo &insn) { + if (!prev_insn) + return false; + + // line entry checks + if (insn.sc.line_entry.IsValid() ^ prev_insn->sc.line_entry.IsValid()) + return false; + if (insn.sc.line_entry.IsValid() && prev_insn->sc.line_entry.IsValid()) + return LineEntry::Compare(insn.sc.line_entry, prev_insn->sc.line_entry) == + 0; + + // function checks + if ((insn.sc.function != nullptr) ^ (prev_insn->sc.function != nullptr)) + return false; + if (insn.sc.function != nullptr && prev_insn->sc.function != nullptr) + return insn.sc.function == prev_insn->sc.function; + + // symbol checks + if ((insn.sc.symbol != nullptr) ^ (prev_insn->sc.symbol != nullptr)) + return false; + if (insn.sc.symbol != nullptr && prev_insn->sc.symbol != nullptr) + return insn.sc.symbol == prev_insn->sc.symbol; + + // module checks + return insn.sc.module_sp == prev_insn->sc.module_sp; +} + /// Dump the symbol context of the given instruction address if it's different /// from the symbol context of the previous instruction in the trace. /// @@ -113,181 +142,164 @@ /// \return /// The symbol context of the current address, which might differ from the /// previous one. -static SymbolContext DumpSymbolContext(Stream &s, const SymbolContext &prev_sc, - Target &target, const Address &address) { - AddressRange range; - if (prev_sc.GetAddressRange(eSymbolContextEverything, 0, - /*inline_block_range*/ false, range) && - range.ContainsFileAddress(address)) - return prev_sc; - - SymbolContext sc; - address.CalculateSymbolContext(&sc, eSymbolContextEverything); - - if (!prev_sc.module_sp && !sc.module_sp) - return sc; - if (prev_sc.module_sp == sc.module_sp && !sc.function && !sc.symbol && - !prev_sc.function && !prev_sc.symbol) - return sc; +static void +DumpInstructionSymbolContext(Stream &s, + Optional prev_insn, + Trace::InstructionSymbolInfo &insn) { + if (IsSameInstructionSymbolContext(prev_insn, insn)) + return; s.Printf(" "); - if (!sc.module_sp) + if (!insn.sc.module_sp) s.Printf("(none)"); - else if (!sc.function && !sc.symbol) + else if (!insn.sc.function && !insn.sc.symbol) s.Printf("%s`(none)", - sc.module_sp->GetFileSpec().GetFilename().AsCString()); + insn.sc.module_sp->GetFileSpec().GetFilename().AsCString()); else - sc.DumpStopContext(&s, &target, address, /*show_fullpath*/ false, - /*show_module*/ true, /*show_inlined_frames*/ false, + insn.sc.DumpStopContext(&s, insn.exe_ctx.GetTargetPtr(), insn.address, + /*show_fullpath*/ false, + /*show_module*/ true, /*show_inlined_frames*/ false, /*show_function_arguments*/ true, /*show_function_name*/ true); s.Printf("\n"); - return sc; -} - -/// Dump an instruction given by its address using a given disassembler, unless -/// the instruction is not present in the disassembler. -/// -/// \param[in] disassembler -/// A disassembler containing a certain instruction list. -/// -/// \param[in] address -/// The address of the instruction to dump. -/// -/// \return -/// \b true if the information could be dumped, \b false otherwise. -static bool TryDumpInstructionInfo(Stream &s, - const DisassemblerSP &disassembler, - const ExecutionContext &exe_ctx, - const Address &address) { - if (!disassembler) - return false; - - if (InstructionSP instruction = - disassembler->GetInstructionList().GetInstructionAtAddress(address)) { - instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, - /*max_opcode_byte_size*/ 0, &exe_ctx, - /*sym_ctx*/ nullptr, /*prev_sym_ctx*/ nullptr, - /*disassembly_addr_format*/ nullptr, - /*max_address_text_size*/ 0); - return true; - } - - return false; } -/// Dump an instruction instruction given by its address. -/// -/// \param[in] prev_disassembler -/// The disassembler that was used to dump the previous instruction in the -/// trace. It is useful to avoid recomputations. -/// -/// \param[in] address -/// The address of the instruction to dump. -/// -/// \return -/// A disassembler that contains the given instruction, which might differ -/// from the previous disassembler. -static DisassemblerSP -DumpInstructionInfo(Stream &s, const SymbolContext &sc, - const DisassemblerSP &prev_disassembler, - ExecutionContext &exe_ctx, const Address &address) { - // We first try to use the previous disassembler - if (TryDumpInstructionInfo(s, prev_disassembler, exe_ctx, address)) - return prev_disassembler; - - // Now we try using the current function's disassembler - if (sc.function) { - DisassemblerSP disassembler = - sc.function->GetInstructions(exe_ctx, nullptr, true); - if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address)) - return disassembler; - } - - // We fallback to disassembly one instruction - Target &target = exe_ctx.GetTargetRef(); - const ArchSpec &arch = target.GetArchitecture(); - AddressRange range(address, arch.GetMaximumOpcodeByteSize() * 1); - DisassemblerSP disassembler = Disassembler::DisassembleRange( - arch, /*plugin_name*/ nullptr, - /*flavor*/ nullptr, target, range, /*prefer_file_cache*/ true); - if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address)) - return disassembler; - return nullptr; +static void DumpInstructionDisassembly(Stream &s, + Trace::InstructionSymbolInfo &insn) { + if (!insn.instruction) + return; + insn.instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, + /*max_opcode_byte_size*/ 0, &insn.exe_ctx, &insn.sc, + /*prev_sym_ctx*/ nullptr, + /*disassembly_addr_format*/ nullptr, + /*max_address_text_size*/ 0); } void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t end_position, bool raw) { - if (!IsTraced(thread)) { + size_t end_position, TraceDumpFormat format) { + Optional instructions_count = GetInstructionCount(thread); + if (!instructions_count) { s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), thread.GetID()); return; } - size_t instructions_count = GetInstructionCount(thread); s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", - thread.GetIndexID(), thread.GetID(), instructions_count); + thread.GetIndexID(), thread.GetID(), *instructions_count); - if (count == 0 || end_position >= instructions_count) + if (count == 0 || end_position >= *instructions_count) return; + int digits_count = GetNumberOfDigits(end_position); size_t start_position = end_position + 1 < count ? 0 : end_position + 1 - count; - - int digits_count = GetNumberOfDigits(end_position); auto printInstructionIndex = [&](size_t index) { s.Printf(" [%*zu] ", digits_count, index); }; bool was_prev_instruction_an_error = false; - Target &target = thread.GetProcess()->GetTarget(); + Optional prev_insn; - SymbolContext sc; - DisassemblerSP disassembler; - ExecutionContext exe_ctx; - target.CalculateExecutionContext(exe_ctx); - - TraverseInstructions( + TraverseInstructionsWithSymbolInfo( thread, start_position, TraceDirection::Forwards, - [&](size_t index, Expected load_address) -> bool { - if (load_address) { - // We print an empty line after a sequence of errors to show more - // clearly that there's a gap in the trace + [&](size_t index, Expected insn) -> bool { + if (!insn) { + printInstructionIndex(index); + s << toString(insn.takeError()); + + prev_insn = None; + was_prev_instruction_an_error = true; + } else { if (was_prev_instruction_an_error) s.Printf(" ...missing instructions\n"); - Address address; - if (!raw) { - target.GetSectionLoadList().ResolveLoadAddress(*load_address, - address); - - sc = DumpSymbolContext(s, sc, target, address); - } + if (format != TraceDumpFormat::Raw) + DumpInstructionSymbolContext(s, prev_insn, *insn); printInstructionIndex(index); - s.Printf("0x%016" PRIx64 " ", *load_address); + s.Printf("0x%016" PRIx64 " ", insn->load_address); - if (!raw) { - disassembler = - DumpInstructionInfo(s, sc, disassembler, exe_ctx, address); - } + if (format != TraceDumpFormat::Raw) + DumpInstructionDisassembly(s, *insn); + prev_insn = *insn; was_prev_instruction_an_error = false; - } else { - printInstructionIndex(index); - s << toString(load_address.takeError()); - was_prev_instruction_an_error = true; - if (!raw) - sc = SymbolContext(); } s.Printf("\n"); - return index < end_position; }); } +void Trace::TraverseInstructionsWithSymbolInfo( + Thread &thread, size_t position, TraceDirection direction, + std::function insn)> + callback) { + InstructionSymbolInfo prev_insn; + + Target &target = thread.GetProcess()->GetTarget(); + ExecutionContext exe_ctx; + target.CalculateExecutionContext(exe_ctx); + const ArchSpec &arch = target.GetArchitecture(); + + auto calculate_symbol_context = [&](const Address &address) { + AddressRange range; + if (prev_insn.sc.GetAddressRange(eSymbolContextEverything, 0, + /*inline_block_range*/ false, range) && + range.ContainsFileAddress(address)) + return prev_insn.sc; + + SymbolContext sc; + address.CalculateSymbolContext(&sc, eSymbolContextEverything); + return sc; + }; + + auto calculate_disass = [&](const Address &address, const SymbolContext &sc) { + if (prev_insn.disassembler) + if (InstructionSP instruction = + prev_insn.disassembler->GetInstructionList() + .GetInstructionAtAddress(address)) + return std::make_tuple(prev_insn.disassembler, instruction); + + if (sc.function) { + DisassemblerSP disassembler = + sc.function->GetInstructions(exe_ctx, nullptr, true); + if (disassembler) + if (InstructionSP instruction = + disassembler->GetInstructionList().GetInstructionAtAddress( + address)) + return std::make_tuple(disassembler, instruction); + } + AddressRange range(address, arch.GetMaximumOpcodeByteSize() * 1); + DisassemblerSP disassembler = Disassembler::DisassembleRange( + arch, /*plugin_name*/ nullptr, + /*flavor*/ nullptr, target, range, /*prefer_file_cache*/ true); + return std::make_tuple(disassembler, + disassembler ? disassembler->GetInstructionList() + .GetInstructionAtAddress(address) + : InstructionSP()); + }; + + TraverseInstructions( + thread, position, direction, + [&](size_t index, Expected load_address) -> bool { + if (!load_address) + return callback(index, load_address.takeError()); + + InstructionSymbolInfo insn; + insn.load_address = *load_address; + insn.exe_ctx = exe_ctx; + target.GetSectionLoadList().ResolveLoadAddress(*load_address, + insn.address); + insn.sc = calculate_symbol_context(insn.address); + std::tie(insn.disassembler, insn.instruction) = + calculate_disass(insn.address, insn.sc); + prev_insn = insn; + return callback(index, insn); + }); +} + Error Trace::Start(const llvm::json::Value &request) { if (!m_live_process) return createStringError(inconvertibleErrorCode(),