diff --git a/lldb/include/lldb/Symbol/LineEntry.h b/lldb/include/lldb/Symbol/LineEntry.h --- a/lldb/include/lldb/Symbol/LineEntry.h +++ b/lldb/include/lldb/Symbol/LineEntry.h @@ -91,11 +91,16 @@ /// \param[in] rhs /// The Right Hand Side const LineEntry object reference. /// + /// \param[in] compare_addresses + /// If \b true, then the addresses of the LineEntry objects will be + /// compared, otherwise only the line, column and file names must match. + /// /// \return /// -1 if lhs < rhs /// 0 if lhs == rhs /// 1 if lhs > rhs - static int Compare(const LineEntry &lhs, const LineEntry &rhs); + static int Compare(const LineEntry &lhs, const LineEntry &rhs, + bool compare_addresses = true); /// Give the range for this LineEntry + any additional LineEntries for this /// same source line that are contiguous. 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" @@ -204,14 +205,43 @@ std::function load_addr)> callback) = 0; + /// Helper structure for \a TraverseInstructionsWithSymbolInfo. + 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 an \a + /// InstructionSymbolInfo object with symbol information for the given + /// instruction, calculated efficiently. + /// + /// \param[in] symbol_scope + /// If not \b 0, then the \a InstructionSymbolInfo will have its + /// SymbolContext calculated up to that level. + /// + /// \param[in] include_disassembler + /// If \b true, then the \a InstructionSymbolInfo will have the + /// \a disassembler and \a instruction objects calculated. + void TraverseInstructionsWithSymbolInfo( + Thread &thread, size_t position, TraceDirection direction, + lldb::SymbolContextItem symbol_scope, bool include_disassembler, + 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/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/Symbol/LineEntry.cpp b/lldb/source/Symbol/LineEntry.cpp --- a/lldb/source/Symbol/LineEntry.cpp +++ b/lldb/source/Symbol/LineEntry.cpp @@ -155,27 +155,30 @@ return LineEntry::Compare(a, b) < 0; } -int LineEntry::Compare(const LineEntry &a, const LineEntry &b) { - int result = Address::CompareFileAddress(a.range.GetBaseAddress(), - b.range.GetBaseAddress()); - if (result != 0) - return result; - - const lldb::addr_t a_byte_size = a.range.GetByteSize(); - const lldb::addr_t b_byte_size = b.range.GetByteSize(); - - if (a_byte_size < b_byte_size) - return -1; - if (a_byte_size > b_byte_size) - return +1; - - // Check for an end sequence entry mismatch after we have determined that the - // address values are equal. If one of the items is an end sequence, we don't - // care about the line, file, or column info. - if (a.is_terminal_entry > b.is_terminal_entry) - return -1; - if (a.is_terminal_entry < b.is_terminal_entry) - return +1; +int LineEntry::Compare(const LineEntry &a, const LineEntry &b, + bool compare_address_ranges) { + if (compare_address_ranges) { + int result = Address::CompareFileAddress(a.range.GetBaseAddress(), + b.range.GetBaseAddress()); + if (result != 0) + return result; + + const lldb::addr_t a_byte_size = a.range.GetByteSize(); + const lldb::addr_t b_byte_size = b.range.GetByteSize(); + + if (a_byte_size < b_byte_size) + return -1; + if (a_byte_size > b_byte_size) + return +1; + + // Check for an end sequence entry mismatch after we have determined that + // the address values are equal. If one of the items is an end sequence, we + // don't care about the line, file, or column info. + if (a.is_terminal_entry > b.is_terminal_entry) + return -1; + if (a.is_terminal_entry < b.is_terminal_entry) + return +1; + } if (a.line < b.line) return -1; 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,53 @@ return num == 0 ? 1 : static_cast(log10(num)) + 1; } +/// Compare the symbol contexts of the provided \a InstructionSymbolInfo objects. +/// +/// \return +/// \a true if both instructions belong to the same scope level analized +/// in the following order: +/// - line +/// - function +/// - symbol +/// - module +static bool +IsSameInstructionSymbolContext(Optional prev_insn, + Trace::InstructionSymbolInfo &insn) { + if (!prev_insn) + return false; + + // 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. + auto is_line_entry_valid = [](const LineEntry &line_entry) { + return line_entry.IsValid() && line_entry.line > 0; + }; + + // line entry checks + if (is_line_entry_valid(insn.sc.line_entry) ^ + is_line_entry_valid(prev_insn->sc.line_entry)) + return false; + if (is_line_entry_valid(insn.sc.line_entry) && + is_line_entry_valid(prev_insn->sc.line_entry)) + return LineEntry::Compare(insn.sc.line_entry, prev_insn->sc.line_entry, + /*compare_addresses*/ false) == 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 +160,174 @@ /// \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, - /*show_function_arguments*/ true, + 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)) { + 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(); - - SymbolContext sc; - DisassemblerSP disassembler; - ExecutionContext exe_ctx; - target.CalculateExecutionContext(exe_ctx); + Optional prev_insn; - 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 + eSymbolContextEverything, /*disassembler*/ true, + [&](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 (!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 (!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, + SymbolContextItem symbol_scope, bool include_disassembler, + std::function insn)> + callback) { + InstructionSymbolInfo prev_insn; + + Target &target = thread.GetProcess()->GetTarget(); + ExecutionContext exe_ctx; + target.CalculateExecutionContext(exe_ctx); + const ArchSpec &arch = target.GetArchitecture(); + + // Find the symbol context for the given address reusing the previous + // instruction's symbol context when possible. + auto calculate_symbol_context = [&](const Address &address) { + AddressRange range; + if (prev_insn.sc.GetAddressRange(symbol_scope, 0, + /*inline_block_range*/ false, range) && + range.ContainsFileAddress(address)) + return prev_insn.sc; + + SymbolContext sc; + address.CalculateSymbolContext(&sc, symbol_scope); + return sc; + }; + + // Find the disassembler for the given address reusing the previous + // instruction's disassembler when possible. + 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) { + if (DisassemblerSP disassembler = + sc.function->GetInstructions(exe_ctx, nullptr, true)) { + if (InstructionSP instruction = + disassembler->GetInstructionList().GetInstructionAtAddress( + address)) + return std::make_tuple(disassembler, instruction); + } + } + // We fallback to a single instruction disassembler + 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); + if (symbol_scope != 0) + insn.sc = calculate_symbol_context(insn.address); + if (include_disassembler) + 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(), diff --git a/lldb/test/API/commands/trace/TestTraceStartStop.py b/lldb/test/API/commands/trace/TestTraceStartStop.py --- a/lldb/test/API/commands/trace/TestTraceStartStop.py +++ b/lldb/test/API/commands/trace/TestTraceStartStop.py @@ -100,7 +100,6 @@ a.out`main \+ 11 at main.cpp:4 \[1\] {ADDRESS_REGEX} movl .* \[2\] {ADDRESS_REGEX} jmp .* ; <\+28> at main.cpp:4 - a.out`main \+ 28 at main.cpp:4 \[3\] {ADDRESS_REGEX} cmpl .* \[4\] {ADDRESS_REGEX} jle .* ; <\+20> at main.cpp:5'''])