diff options
Diffstat (limited to 'lldb/source/Target/TraceInstructionDumper.cpp')
-rw-r--r-- | lldb/source/Target/TraceInstructionDumper.cpp | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/lldb/source/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp new file mode 100644 index 000000000000..dc1e86481c36 --- /dev/null +++ b/lldb/source/Target/TraceInstructionDumper.cpp @@ -0,0 +1,292 @@ +//===-- 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(); + } +} |