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 @@ -22,6 +22,60 @@ namespace lldb_private { +/// Class that represents a decoded instruction from a trace. It's supposed to +/// have a minimal memory footprint as the number of instructions can be in the +/// order of millions. +/// +/// For complex operations, \a lldb_private::Instruction can be used sparingly. +/// +/// This class assumes that errors will be extremely rare compared to the number +/// of correct instructions and storing them as \a ConstString should be fine. +class TraceInstruction { +public: + TraceInstruction(lldb::addr_t load_address, uint8_t byte_size, + lldb::TraceInstructionType instruction_type); + + /// Constructor for an instruction that couldn't be decoded + TraceInstruction(llvm::Error error); + + /// This constructs a failed instruction, i.e. \a GetError() returns \b true. + TraceInstruction(); + + /// \return + /// The architecture-agnostic instruction type + lldb::TraceInstructionType GetInstructionType() const { + return m_instruction_type; + } + + /// \return + /// The load adress (aka instruction pointer) + lldb::addr_t GetLoadAddress() const { return m_data.m_load_address; } + + /// \return + /// The error message or \b nullptr if not an error. + const char *GetError() const { + if (m_is_error) + return m_data.m_error; + return nullptr; + } + + /// \return + /// The size in bytes of the instruction. + uint8_t GetByteSize() const { return m_byte_size; } + + bool IsError() const { return m_is_error; } + +private: + uint8_t m_is_error; + uint8_t m_byte_size; + lldb::TraceInstructionType m_instruction_type; + + union { + lldb::addr_t m_load_address; + const char *m_error; + } m_data; +}; + /// \class Trace Trace.h "lldb/Target/Trace.h" /// A plug-in interface definition class for trace information. /// @@ -201,7 +255,7 @@ /// the iteration stops. virtual void TraverseInstructions( Thread &thread, size_t position, TraceDirection direction, - std::function load_addr)> + std::function callback) = 0; /// Get the number of available instructions in the trace of the given thread. diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -781,6 +781,34 @@ eTraceTypeProcessorTrace }; +/// Architecture-agnostic categorization of instructions. Useful for doing +/// analysis on traces. +enum TraceInstructionType : unsigned char { + /// The instruction is not recognized by LLDB + eTraceInstructionUnknown = 0, + /// The instruction is something not listed below + eTraceInstructionOther, + /// The instruction is a near (function) call + eTraceInstructionCall, + /// The instruction is a near (function) return + eTraceInstructionReturn, + /// The instruction is a near unconditional jump + eTraceInstructionJump, + /// The instruction is a near conditional jump + eTraceInstructionCondJump, + /// The instruction is a call-like far transfer + /// e.g. SYSCALL, SYSENTER, or FAR CALL + eTraceInstructionFarCall, + /// The instruction is a return-like far transfer + /// e.g. SYSRET, SYSEXIT, IRET, or FAR RET + eTraceInstructionFarReturn, + /// The instruction is a jump-like far transfer + /// e.g. FAR JMP + eTraceInstructionFarJump, + /// The instruction is a PTWRITE or similar + eTraceInstructionTraceWrite, +}; + enum StructuredDataType { eStructuredDataTypeInvalid = -1, eStructuredDataTypeNull = 0, diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -11,7 +11,6 @@ #include -#include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "lldb/Target/Trace.h" @@ -22,83 +21,6 @@ namespace lldb_private { namespace trace_intel_pt { -/// Class for representing a libipt decoding error. -class IntelPTError : public llvm::ErrorInfo { -public: - static char ID; - - /// \param[in] libipt_error_code - /// Negative number returned by libipt when decoding the trace and - /// signaling errors. - /// - /// \param[in] address - /// Optional instruction address. When decoding an individual instruction, - /// its address might be available in the \a pt_insn object, and should be - /// passed to this constructor. Other errors don't have an associated - /// address. - IntelPTError(int libipt_error_code, - lldb::addr_t address = LLDB_INVALID_ADDRESS); - - std::error_code convertToErrorCode() const override { - return llvm::errc::not_supported; - } - - void log(llvm::raw_ostream &OS) const override; - -private: - int m_libipt_error_code; - lldb::addr_t m_address; -}; - -/// \class IntelPTInstruction -/// An instruction obtained from decoding a trace. It is either an actual -/// instruction or an error indicating a gap in the trace. -/// -/// Gaps in the trace can come in a few flavors: -/// - tracing gaps (e.g. tracing was paused and then resumed) -/// - tracing errors (e.g. buffer overflow) -/// - decoding errors (e.g. some memory region couldn't be decoded) -/// As mentioned, any gap is represented as an error in this class. -class IntelPTInstruction { -public: - IntelPTInstruction(const pt_insn &pt_insn) : m_pt_insn(pt_insn) {} - - /// Error constructor - /// - /// libipt errors should use the underlying \a IntelPTError class. - IntelPTInstruction(llvm::Error err) { - llvm::handleAllErrors(std::move(err), - [&](std::unique_ptr info) { - m_error = std::move(info); - }); - } - - /// Check if this object represents an error (i.e. a gap). - /// - /// \return - /// Whether this object represents an error. - bool IsError() const; - - /// \return - /// The instruction pointer address, or an \a llvm::Error if it is an - /// error. - llvm::Expected GetLoadAddress() const; - - /// \return - /// An \a llvm::Error object if this class corresponds to an Error, or an - /// \a llvm::Error::success otherwise. - llvm::Error ToError() const; - - IntelPTInstruction(IntelPTInstruction &&other) = default; - -private: - IntelPTInstruction(const IntelPTInstruction &other) = delete; - const IntelPTInstruction &operator=(const IntelPTInstruction &other) = delete; - - pt_insn m_pt_insn; - std::unique_ptr m_error; -}; - /// \class DecodedThread /// Class holding the instructions and function call hierarchy obtained from /// decoding a trace, as well as a position cursor used when reverse debugging @@ -108,7 +30,7 @@ /// stopped at. See \a Trace::GetCursorPosition for more information. class DecodedThread { public: - DecodedThread(std::vector &&instructions) + DecodedThread(std::vector &&instructions) : m_instructions(std::move(instructions)), m_position(GetLastPosition()) { } @@ -121,7 +43,7 @@ /// /// \return /// The instructions of the trace. - llvm::ArrayRef GetInstructions() const; + llvm::ArrayRef GetInstructions() const; /// \return /// The current position of the cursor of this trace, or 0 if there are no @@ -141,7 +63,7 @@ /// The index of the last element of the trace, or 0 if empty. size_t GetLastPosition() const; - std::vector m_instructions; + std::vector m_instructions; size_t m_position; }; diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -14,45 +14,11 @@ using namespace lldb_private::trace_intel_pt; using namespace llvm; -char IntelPTError::ID; - -IntelPTError::IntelPTError(int libipt_error_code, lldb::addr_t address) - : m_libipt_error_code(libipt_error_code), m_address(address) { - assert(libipt_error_code < 0); -} - -void IntelPTError::log(llvm::raw_ostream &OS) const { - const char *libipt_error_message = pt_errstr(pt_errcode(m_libipt_error_code)); - if (m_address != LLDB_INVALID_ADDRESS && m_address > 0) { - write_hex(OS, m_address, HexPrintStyle::PrefixLower, 18); - OS << " "; - } - OS << "error: " << libipt_error_message; -} - -bool IntelPTInstruction::IsError() const { return (bool)m_error; } - -Expected IntelPTInstruction::GetLoadAddress() const { - if (IsError()) - return ToError(); - return m_pt_insn.ip; -} - -Error IntelPTInstruction::ToError() const { - if (!IsError()) - return Error::success(); - - if (m_error->isA()) - return make_error(static_cast(*m_error)); - return make_error(m_error->message(), - m_error->convertToErrorCode()); -} - size_t DecodedThread::GetLastPosition() const { return m_instructions.empty() ? 0 : m_instructions.size() - 1; } -ArrayRef DecodedThread::GetInstructions() const { +ArrayRef DecodedThread::GetInstructions() const { return makeArrayRef(m_instructions); } diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -8,19 +8,32 @@ #include "IntelPTDecoder.h" #include "llvm/Support/MemoryBuffer.h" +#include #include "TraceIntelPT.h" #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadPostMortemTrace.h" -#include "lldb/Utility/StringExtractor.h" using namespace lldb; using namespace lldb_private; using namespace lldb_private::trace_intel_pt; using namespace llvm; +static llvm::Error +CreateIntelPTError(int libipt_error_code, + lldb::addr_t address = LLDB_INVALID_ADDRESS) { + const char *libipt_error_message = pt_errstr(pt_errcode(libipt_error_code)); + if (address != LLDB_INVALID_ADDRESS && address > 0) { + return createStringError(inconvertibleErrorCode(), + "0x%16.16" PRIx64 " error: %s", address, + libipt_error_message); + } + return createStringError(inconvertibleErrorCode(), "error: %s", + libipt_error_message); +} + /// Move the decoder forward to the next synchronization point (i.e. next PSB /// packet). /// @@ -88,6 +101,31 @@ return 0; } +TraceInstructionType GetTraceInstructionType(const pt_insn &insn) { + switch (insn.iclass) { + case ptic_other: + return lldb::eTraceInstructionOther; + case ptic_call: + return lldb::eTraceInstructionCall; + case ptic_return: + return lldb::eTraceInstructionReturn; + case ptic_jump: + return lldb::eTraceInstructionJump; + case ptic_cond_jump: + return lldb::eTraceInstructionCondJump; + case ptic_far_call: + return lldb::eTraceInstructionFarCall; + case ptic_far_return: + return lldb::eTraceInstructionFarReturn; + case ptic_far_jump: + return lldb::eTraceInstructionFarJump; + case ptic_ptwrite: + return lldb::eTraceInstructionTraceWrite; + default: + return lldb::eTraceInstructionUnknown; + } +} + /// Decode all the instructions from a configured decoder. /// The decoding flow is based on /// https://github.com/intel/libipt/blob/master/doc/howto_libipt.md#the-instruction-flow-decode-loop @@ -103,9 +141,9 @@ /// /// \return /// The decoded instructions. -static std::vector +static std::vector DecodeInstructions(pt_insn_decoder &decoder) { - std::vector instructions; + std::vector instructions; while (true) { int errcode = FindNextSynchronizationPoint(decoder); @@ -113,7 +151,7 @@ break; if (errcode < 0) { - instructions.emplace_back(make_error(errcode)); + instructions.emplace_back(CreateIntelPTError(errcode)); break; } @@ -122,7 +160,7 @@ while (true) { errcode = ProcessPTEvents(decoder, errcode); if (errcode < 0) { - instructions.emplace_back(make_error(errcode)); + instructions.emplace_back(CreateIntelPTError(errcode)); break; } pt_insn insn; @@ -132,11 +170,12 @@ break; if (errcode < 0) { - instructions.emplace_back(make_error(errcode, insn.ip)); + instructions.emplace_back(CreateIntelPTError(errcode, insn.ip)); break; } - instructions.emplace_back(insn); + instructions.emplace_back(insn.ip, insn.size, + GetTraceInstructionType(insn)); } } @@ -159,7 +198,7 @@ return bytes_read; } -static Expected> +static Expected> DecodeInMemoryTrace(Process &process, TraceIntelPT &trace_intel_pt, MutableArrayRef buffer) { Expected cpu_info = trace_intel_pt.GetCPUInfo(); @@ -171,14 +210,14 @@ config.cpu = *cpu_info; if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) - return make_error(errcode); + return CreateIntelPTError(errcode); config.begin = buffer.data(); config.end = buffer.data() + buffer.size(); pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); if (!decoder) - return make_error(-pte_nomem); + return CreateIntelPTError(-pte_nomem); pt_image *image = pt_insn_get_image(decoder); @@ -186,13 +225,13 @@ assert(errcode == 0); (void)errcode; - std::vector instructions = DecodeInstructions(*decoder); + std::vector instructions = DecodeInstructions(*decoder); pt_insn_free_decoder(decoder); return instructions; } -static Expected> +static Expected> DecodeTraceFile(Process &process, TraceIntelPT &trace_intel_pt, const FileSpec &trace_file) { ErrorOr> trace_or_error = @@ -209,7 +248,7 @@ return DecodeInMemoryTrace(process, trace_intel_pt, trace_data); } -static Expected> +static Expected> DecodeLiveThread(Thread &thread, TraceIntelPT &trace) { Expected> buffer = trace.GetLiveThreadBuffer(thread.GetID()); @@ -233,7 +272,7 @@ : m_trace_thread(trace_thread), m_trace(trace) {} DecodedThread PostMortemThreadDecoder::DoDecode() { - if (Expected> instructions = + if (Expected> instructions = DecodeTraceFile(*m_trace_thread->GetProcess(), m_trace, m_trace_thread->GetTraceFile())) return DecodedThread(std::move(*instructions)); @@ -245,7 +284,7 @@ : m_thread_sp(thread.shared_from_this()), m_trace(trace) {} DecodedThread LiveThreadDecoder::DoDecode() { - if (Expected> instructions = + if (Expected> instructions = DecodeLiveThread(*m_thread_sp, m_trace)) return DecodedThread(std::move(*instructions)); else 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 @@ -67,7 +67,7 @@ void TraverseInstructions( Thread &thread, size_t position, TraceDirection direction, - std::function load_addr)> + std::function callback) override; llvm::Optional GetInstructionCount(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 @@ -108,18 +108,18 @@ void TraceIntelPT::TraverseInstructions( Thread &thread, size_t position, TraceDirection direction, - std::function load_addr)> + std::function callback) { const DecodedThread *decoded_thread = Decode(thread); if (!decoded_thread) return; - ArrayRef instructions = decoded_thread->GetInstructions(); + ArrayRef instructions = decoded_thread->GetInstructions(); ssize_t delta = direction == TraceDirection::Forwards ? 1 : -1; for (ssize_t i = position; i < (ssize_t)instructions.size() && i >= 0; i += delta) - if (!callback(i, instructions[i].GetLoadAddress())) + if (!callback(i, instructions[i])) break; } 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 @@ -52,6 +52,23 @@ } // namespace json } // namespace llvm +TraceInstruction::TraceInstruction() : m_is_error(true) { + m_data.m_error = "invalid instruction"; +} + +TraceInstruction::TraceInstruction(lldb::addr_t load_address, uint8_t byte_size, + lldb::TraceInstructionType instruction_type) + : m_is_error(false), m_byte_size(byte_size), + m_instruction_type(instruction_type) { + m_data.m_load_address = load_address; +} + +TraceInstruction::TraceInstruction(Error error) : m_is_error(true) { + // We are assuming that errors will be rare, so using the ConstrString pool + // helps us make this class smaller. + m_data.m_error = ConstString(toString(std::move(error))).GetCString(); +} + static Error createInvalidPlugInError(StringRef plugin_name) { return createStringError( std::errc::invalid_argument, @@ -124,9 +141,9 @@ struct InstructionSymbolInfo { SymbolContext sc; Address address; - lldb::addr_t load_address; + TraceInstruction instruction; lldb::DisassemblerSP disassembler; - lldb::InstructionSP instruction; + lldb::InstructionSP instruction_sp; lldb_private::ExecutionContext exe_ctx; }; @@ -144,7 +161,7 @@ Trace &trace, Thread &thread, size_t position, Trace::TraceDirection direction, SymbolContextItem symbol_scope, bool include_disassembler, - std::function insn)> + std::function callback) { InstructionSymbolInfo prev_insn; @@ -199,18 +216,18 @@ trace.TraverseInstructions( thread, position, direction, - [&](size_t index, Expected load_address) -> bool { - if (!load_address) - return callback(index, load_address.takeError()); - + [&](size_t index, const TraceInstruction &instruction) -> bool { InstructionSymbolInfo insn; - insn.load_address = *load_address; + insn.instruction = instruction; + if (instruction.IsError()) + return callback(index, insn); + insn.exe_ctx = exe_ctx; - insn.address.SetLoadAddress(*load_address, &target); + insn.address.SetLoadAddress(instruction.GetLoadAddress(), &target); if (symbol_scope != 0) insn.sc = calculate_symbol_context(insn.address); if (include_disassembler) - std::tie(insn.disassembler, insn.instruction) = + std::tie(insn.disassembler, insn.instruction_sp) = calculate_disass(insn.address, insn.sc); prev_insn = insn; return callback(index, insn); @@ -268,7 +285,7 @@ static void DumpInstructionSymbolContext(Stream &s, Optional prev_insn, - InstructionSymbolInfo &insn) { + const InstructionSymbolInfo &insn) { if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn)) return; @@ -288,16 +305,16 @@ s.Printf("\n"); } -static void DumpInstructionDisassembly(Stream &s, InstructionSymbolInfo &insn) { - if (!insn.instruction) +static void DumpInstructionDisassembly(Stream &s, + const InstructionSymbolInfo &insn) { + if (insn.instruction.IsError()) 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); + insn.instruction_sp->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, @@ -328,10 +345,10 @@ TraverseInstructionsWithSymbolInfo( *this, thread, start_position, TraceDirection::Forwards, eSymbolContextEverything, /*disassembler*/ true, - [&](size_t index, Expected insn) -> bool { - if (!insn) { + [&](size_t index, const InstructionSymbolInfo &insn) -> bool { + if (insn.instruction.IsError()) { printInstructionIndex(index); - s << toString(insn.takeError()); + s << insn.instruction.GetError(); prev_insn = None; was_prev_instruction_an_error = true; @@ -340,15 +357,15 @@ s.Printf(" ...missing instructions\n"); if (!raw) - DumpInstructionSymbolContext(s, prev_insn, *insn); + DumpInstructionSymbolContext(s, prev_insn, insn); printInstructionIndex(index); - s.Printf("0x%016" PRIx64, insn->load_address); + s.Printf("0x%016" PRIx64, insn.instruction.GetLoadAddress()); if (!raw) - DumpInstructionDisassembly(s, *insn); + DumpInstructionDisassembly(s, insn); - prev_insn = *insn; + prev_insn = insn; was_prev_instruction_an_error = false; }