| //===-- TraceInstructionDumper.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/TraceInstructionDumper.h" |
| |
| #include "lldb/Core/Module.h" |
| #include "lldb/Symbol/Function.h" |
| #include "lldb/Target/ExecutionContext.h" |
| #include "lldb/Target/Process.h" |
| #include "lldb/Target/SectionLoadList.h" |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace llvm; |
| |
| TraceInstructionDumper::TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, |
| int initial_index, bool raw, |
| bool show_tsc) |
| : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw), |
| m_show_tsc(show_tsc) {} |
| |
| /// \return |
| /// Return \b true if the cursor could move one step. |
| bool TraceInstructionDumper::TryMoveOneStep() { |
| if (!m_cursor_up->Next()) { |
| SetNoMoreData(); |
| return false; |
| } |
| m_index += m_cursor_up->IsForwards() ? 1 : -1; |
| return true; |
| } |
| |
| /// \return |
| /// The number of characters that would be needed to print the given |
| /// integer. |
| static int GetNumberOfChars(int num) { |
| if (num == 0) |
| return 1; |
| return (num < 0 ? 1 : 0) + static_cast<int>(log10(abs(num))) + 1; |
| } |
| |
| /// Helper struct that holds symbol, disassembly and address information of an |
| /// instruction. |
| struct InstructionSymbolInfo { |
| SymbolContext sc; |
| Address address; |
| lldb::addr_t load_address; |
| lldb::DisassemblerSP disassembler; |
| lldb::InstructionSP instruction; |
| lldb_private::ExecutionContext exe_ctx; |
| }; |
| |
| // 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 InstructionSymbolInfo |
| /// objects. |
| /// |
| /// \return |
| /// \a true if both instructions belong to the same scope level analized |
| /// in the following order: |
| /// - module |
| /// - symbol |
| /// - function |
| /// - line |
| static bool |
| IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn, |
| const InstructionSymbolInfo &insn) { |
| // 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; |
| else if (insn.sc.function != prev_insn.sc.function) |
| return false; |
| |
| // line entry checks |
| 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; |
| } |
| |
| /// Dump the symbol context of the given instruction address if it's different |
| /// from the symbol context of the previous instruction in the trace. |
| /// |
| /// \param[in] prev_sc |
| /// The symbol context of the previous instruction in the trace. |
| /// |
| /// \param[in] address |
| /// The address whose symbol information will be dumped. |
| /// |
| /// \return |
| /// The symbol context of the current address, which might differ from the |
| /// previous one. |
| static void |
| DumpInstructionSymbolContext(Stream &s, |
| Optional<InstructionSymbolInfo> prev_insn, |
| InstructionSymbolInfo &insn) { |
| if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn)) |
| return; |
| |
| s.Printf(" "); |
| |
| if (!insn.sc.module_sp) |
| s.Printf("(none)"); |
| else if (!insn.sc.function && !insn.sc.symbol) |
| s.Printf("%s`(none)", |
| insn.sc.module_sp->GetFileSpec().GetFilename().AsCString()); |
| else |
| 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"); |
| } |
| |
| static void DumpInstructionDisassembly(Stream &s, InstructionSymbolInfo &insn) { |
| if (!insn.instruction) |
| return; |
| s.Printf(" "); |
| 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 TraceInstructionDumper::SetNoMoreData() { m_no_more_data = true; } |
| |
| bool TraceInstructionDumper::HasMoreData() { return !m_no_more_data; } |
| |
| void TraceInstructionDumper::DumpInstructions(Stream &s, size_t count) { |
| ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP(); |
| if (!thread_sp) { |
| s.Printf("invalid thread"); |
| return; |
| } |
| |
| s.Printf("thread #%u: tid = %" PRIu64 "\n", thread_sp->GetIndexID(), |
| thread_sp->GetID()); |
| |
| int digits_count = GetNumberOfChars( |
| m_cursor_up->IsForwards() ? m_index + count - 1 : m_index - count + 1); |
| bool was_prev_instruction_an_error = false; |
| |
| auto printMissingInstructionsMessage = [&]() { |
| s.Printf(" ...missing instructions\n"); |
| }; |
| |
| auto printInstructionIndex = [&]() { |
| s.Printf(" [%*d] ", digits_count, m_index); |
| |
| if (m_show_tsc) { |
| s.Printf("[tsc="); |
| |
| if (Optional<uint64_t> timestamp = m_cursor_up->GetTimestampCounter()) |
| s.Printf("0x%016" PRIx64, *timestamp); |
| else |
| s.Printf("unavailable"); |
| |
| s.Printf("] "); |
| } |
| }; |
| |
| InstructionSymbolInfo prev_insn_info; |
| |
| Target &target = thread_sp->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 calculateSymbolContext = [&](const Address &address) { |
| AddressRange range; |
| if (prev_insn_info.sc.GetAddressRange(eSymbolContextEverything, 0, |
| /*inline_block_range*/ false, |
| range) && |
| range.Contains(address)) |
| return prev_insn_info.sc; |
| |
| SymbolContext sc; |
| address.CalculateSymbolContext(&sc, eSymbolContextEverything); |
| return sc; |
| }; |
| |
| // Find the disassembler for the given address reusing the previous |
| // instruction's disassembler when possible. |
| auto calculateDisass = [&](const Address &address, const SymbolContext &sc) { |
| if (prev_insn_info.disassembler) { |
| if (InstructionSP instruction = |
| prev_insn_info.disassembler->GetInstructionList() |
| .GetInstructionAtAddress(address)) |
| return std::make_tuple(prev_insn_info.disassembler, instruction); |
| } |
| |
| if (sc.function) { |
| if (DisassemblerSP disassembler = |
| sc.function->GetInstructions(exe_ctx, nullptr)) { |
| 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()); |
| DisassemblerSP disassembler = |
| Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, |
| /*flavor*/ nullptr, target, range); |
| return std::make_tuple(disassembler, |
| disassembler ? disassembler->GetInstructionList() |
| .GetInstructionAtAddress(address) |
| : InstructionSP()); |
| }; |
| |
| for (size_t i = 0; i < count; i++) { |
| if (!HasMoreData()) { |
| s.Printf(" no more data\n"); |
| break; |
| } |
| |
| if (Error err = m_cursor_up->GetError()) { |
| if (!m_cursor_up->IsForwards() && !was_prev_instruction_an_error) |
| printMissingInstructionsMessage(); |
| |
| was_prev_instruction_an_error = true; |
| |
| printInstructionIndex(); |
| s << toString(std::move(err)); |
| } else { |
| if (m_cursor_up->IsForwards() && was_prev_instruction_an_error) |
| printMissingInstructionsMessage(); |
| |
| was_prev_instruction_an_error = false; |
| |
| InstructionSymbolInfo insn_info; |
| |
| if (!m_raw) { |
| insn_info.load_address = m_cursor_up->GetLoadAddress(); |
| insn_info.exe_ctx = exe_ctx; |
| insn_info.address.SetLoadAddress(insn_info.load_address, &target); |
| insn_info.sc = calculateSymbolContext(insn_info.address); |
| std::tie(insn_info.disassembler, insn_info.instruction) = |
| calculateDisass(insn_info.address, insn_info.sc); |
| |
| DumpInstructionSymbolContext(s, prev_insn_info, insn_info); |
| } |
| |
| printInstructionIndex(); |
| s.Printf("0x%016" PRIx64, m_cursor_up->GetLoadAddress()); |
| |
| if (!m_raw) |
| DumpInstructionDisassembly(s, insn_info); |
| |
| prev_insn_info = insn_info; |
| } |
| |
| s.Printf("\n"); |
| TryMoveOneStep(); |
| } |
| } |