diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h --- a/lldb/include/lldb/Core/Disassembler.h +++ b/lldb/include/lldb/Core/Disassembler.h @@ -271,6 +271,13 @@ lldb::InstructionSP GetInstructionAtIndex(size_t idx) const; + /// Get the instruction at the given address. + /// + /// \return + /// A valid \a InstructionSP if the address could be found, or null + /// otherwise. + lldb::InstructionSP GetInstructionAtAddress(const Address &addr); + //------------------------------------------------------------------ /// Get the index of the next branch instruction. /// diff --git a/lldb/include/lldb/Symbol/SymbolContext.h b/lldb/include/lldb/Symbol/SymbolContext.h --- a/lldb/include/lldb/Symbol/SymbolContext.h +++ b/lldb/include/lldb/Symbol/SymbolContext.h @@ -139,11 +139,19 @@ /// be printed. In disassembly formatting, where we want a format /// like "<*+36>", this should be false and "*" will be printed /// instead. + /// + /// \param[in] show_inline_callsite_line_info + /// When processing an inline block, the line info of the callsite + /// is dumped if this flag is \b true, otherwise the line info + /// of the actual inlined function is dumped. + /// + /// \return + /// \b true if some text was dumped, \b false otherwise. bool DumpStopContext(Stream *s, ExecutionContextScope *exe_scope, const Address &so_addr, bool show_fullpaths, bool show_module, bool show_inlined_frames, - bool show_function_arguments, - bool show_function_name) const; + bool show_function_arguments, bool show_function_name, + bool show_inline_callsite_line_info = true) const; /// Get the address range contained within a symbol context. /// 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 @@ -35,6 +35,11 @@ class Trace : public PluginInterface, public std::enable_shared_from_this { public: + enum class TraceDirection { + Forwards = 0, + Backwards, + }; + /// 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 @@ -98,12 +103,24 @@ /// The JSON schema of this Trace plug-in. virtual llvm::StringRef GetSchema() = 0; - /// Dump \a count instructions of the given thread's \a Trace starting at the - /// \a start_position position in reverse order. + /// Each decoded thread contains a cursor to the current position the user is + /// stopped at. When reverse debugging, each operation like reverse-next or + /// reverse-continue will move this cursor, which is then picked by any + /// subsequent dump or reverse operation. + /// + /// The initial position for this cursor is the last element of the thread, + /// which is the most recent chronologically. + /// + /// \return + /// The current position of the thread's trace or \b 0 if empty. + virtual size_t GetCursorPosition(const Thread &thread) = 0; + + /// Dump \a count instructions of the given thread's trace ending at the + /// given \a end_position position. /// - /// The instructions are indexed in reverse order, which means that the \a - /// start_position 0 represents the last instruction of the trace - /// chronologically. + /// The instructions are printed along with their indices or positions, which + /// are increasing chronologically. This means that the \a index 0 represents + /// the oldest instruction of the trace chronologically. /// /// \param[in] thread /// The thread whose trace will be dumped. @@ -114,10 +131,54 @@ /// \param[in] count /// The number of instructions to print. /// - /// \param[in] start_position - /// The position of the first instruction to print. + /// \param[in] end_position + /// The position of the last instruction to print. + /// + /// \param[in] raw + /// Dump only instruction addresses without disassembly nor symbol + /// information. void DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const; + size_t end_position, bool raw); + + /// Run the provided callback on the instructions of the trace of the given + /// thread. + /// + /// The instructions will be traversed starting at the given \a position + /// sequentially until the callback returns \b false, in which case no more + /// instructions are inspected. + /// + /// The purpose of this method is to allow inspecting traced instructions + /// without exposing the internal representation of how they are stored on + /// memory. + /// + /// \param[in] thread + /// The thread whose trace will be traversed. + /// + /// \param[in] position + /// The instruction position to start iterating on. + /// + /// \param[in] direction + /// If \b TraceDirection::Forwards, then then instructions will be + /// traversed forwards chronologically, i.e. with incrementing indices. If + /// \b TraceDirection::Backwards, the traversal is done backwards + /// chronologically, i.e. with decrementing indices. + /// + /// \param[in] callback + /// The callback to execute on each instruction. If it returns \b false, + /// the iteration stops. + virtual void TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function load_addr)> + callback) = 0; + + /// 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(const Thread &thread) = 0; }; } // namespace lldb_private diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -2113,7 +2113,7 @@ return status. """ # Fail fast if 'cmd' is not meaningful. - if not cmd or len(cmd) == 0: + if cmd is None: raise Exception("Bad 'cmd' parameter encountered") trace = (True if traceAlways else trace) 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 @@ -2200,15 +2200,19 @@ m_count = count; break; } - case 's': { - int32_t start_position; - if (option_arg.empty() || option_arg.getAsInteger(0, start_position) || - start_position < 0) + case 'p': { + int32_t position; + if (option_arg.empty() || option_arg.getAsInteger(0, position) || + position < 0) error.SetErrorStringWithFormat( "invalid integer value for option '%s'", option_arg.str().c_str()); else - m_start_position = start_position; + m_position = position; + break; + } + case 'r': { + m_raw = true; break; } default: @@ -2219,19 +2223,20 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_count = kDefaultCount; - m_start_position = kDefaultStartPosition; + m_position = llvm::None; + m_raw = false; } llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_thread_trace_dump_instructions_options); } - static const uint32_t kDefaultCount = 20; - static const uint32_t kDefaultStartPosition = 0; + static const size_t kDefaultCount = 20; // Instance variables to hold the values for command options. - uint32_t m_count; - uint32_t m_start_position; + size_t m_count; + llvm::Optional m_position; + bool m_raw; }; CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) @@ -2253,30 +2258,24 @@ uint32_t index) override { current_command_args.GetCommandString(m_repeat_command); m_create_repeat_command_just_invoked = true; + m_consecutive_repetitions = 0; return m_repeat_command.c_str(); } protected: bool DoExecute(Args &args, CommandReturnObject &result) override { + if (IsRepeatCommand()) + m_consecutive_repetitions++; bool status = CommandObjectIterateOverThreads::DoExecute(args, result); - PrepareRepeatArguments(); - return status; - } - void PrepareRepeatArguments() { - m_repeat_start_position = m_options.m_count + GetStartPosition(); m_create_repeat_command_just_invoked = false; + return status; } bool IsRepeatCommand() { return !m_repeat_command.empty() && !m_create_repeat_command_just_invoked; } - uint32_t GetStartPosition() { - return IsRepeatCommand() ? m_repeat_start_position - : m_options.m_start_position; - } - bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); if (!trace_sp) { @@ -2287,8 +2286,15 @@ ThreadSP thread_sp = m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); - trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(), - m_options.m_count, GetStartPosition()); + size_t count = m_options.m_count; + ssize_t position = m_options.m_position.getValueOr( + trace_sp->GetCursorPosition(*thread_sp)) - + m_consecutive_repetitions * count; + if (position < 0) + result.SetError("error: no more data"); + else + trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(), + count, position, m_options.m_raw); return true; } @@ -2297,7 +2303,7 @@ // Repeat command helpers std::string m_repeat_command; bool m_create_repeat_command_just_invoked; - uint32_t m_repeat_start_position; + size_t m_consecutive_repetitions = 0; }; // CommandObjectMultiwordTraceDump diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1012,16 +1012,14 @@ let Command = "thread trace dump instructions" in { def thread_trace_dump_instructions_count : Option<"count", "c">, Group<1>, Arg<"Count">, - Desc<"The number of instructions to display starting at the current " - "position in reverse order chronologically.">; - def thread_trace_dump_instructions_start_position: - Option<"start-position", "s">, + Desc<"The number of instructions to display ending at the current position.">; + def thread_trace_dump_instructions_position : Option<"position", "p">, Group<1>, Arg<"Index">, - Desc<"The position of the first instruction to print. Defaults to the " - "current position, i.e. where the thread is stopped. The instructions are " - "indexed in reverse order, which means that a start position of 0 refers " - "to the last instruction chronologically.">; + Desc<"The position to use instead of the current position of the trace.">; + def thread_trace_dump_instructions_raw : Option<"raw", "r">, + Group<1>, + Desc<"Dump only instruction address without disassembly nor symbol information.">; } let Command = "type summary add" in { diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp --- a/lldb/source/Core/Disassembler.cpp +++ b/lldb/source/Core/Disassembler.cpp @@ -952,6 +952,13 @@ return inst_sp; } +InstructionSP InstructionList::GetInstructionAtAddress(const Address &address) { + uint32_t index = GetIndexOfInstructionAtAddress(address); + if (index != UINT32_MAX) + return GetInstructionAtIndex(index); + return nullptr; +} + void InstructionList::Dump(Stream *s, bool show_address, bool show_bytes, const ExecutionContext *exe_ctx) { const uint32_t max_opcode_byte_size = GetMaxOpcocdeByteSize(); diff --git a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt --- a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -10,6 +10,8 @@ find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) add_lldb_library(lldbPluginTraceIntelPT PLUGIN + DecodedThread.cpp + IntelPTDecoder.cpp TraceIntelPT.cpp TraceIntelPTSessionFileParser.cpp diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -0,0 +1,146 @@ +//===-- DecodedThread.h -----------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H + +#include + +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" + +#include "lldb/Target/Trace.h" + +#include "intel-pt.h" + +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 +/// the trace. +/// +/// Each decoded thread contains a cursor to the current position the user is +/// stopped at. See \a Trace::GetCursorPosition for more information. +class DecodedThread { +public: + DecodedThread(std::vector &&instructions) + : m_instructions(std::move(instructions)), m_position(GetLastPosition()) { + } + + /// Get the instructions from the decoded trace. Some of them might indicate + /// errors (i.e. gaps) in the trace. + /// + /// \return + /// The instructions of the trace. + llvm::ArrayRef GetInstructions() const; + + /// \return + /// The current position of the cursor of this trace, or 0 if there are no + /// instructions. + size_t GetCursorPosition() const; + + /// Change the position of the cursor of this trace. If this value is to high, + /// the new position will be set as the last instruction of the trace. + /// + /// \return + /// The effective new position. + size_t SetCursorPosition(size_t new_position); + /// \} + +private: + /// \return + /// The index of the last element of the trace, or 0 if empty. + size_t GetLastPosition() const; + + std::vector m_instructions; + size_t m_position; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -0,0 +1,64 @@ +//===-- DecodedThread.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 "DecodedThread.h" + +#include "lldb/Utility/StreamString.h" + +using namespace lldb_private; +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 { + return makeArrayRef(m_instructions); +} + +size_t DecodedThread::GetCursorPosition() const { return m_position; } + +size_t DecodedThread::SetCursorPosition(size_t new_position) { + m_position = std::min(new_position, GetLastPosition()); + return m_position; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -0,0 +1,52 @@ +//===-- IntelPTDecoder.h --======--------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H + +#include "intel-pt.h" + +#include "DecodedThread.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" + +namespace lldb_private { +namespace trace_intel_pt { + +/// \a lldb_private::ThreadTrace decoder that stores the output from decoding, +/// avoiding recomputations, as decoding is expensive. +class ThreadTraceDecoder { +public: + /// \param[in] trace_thread + /// The thread whose trace file will be decoded. + /// + /// \param[in] pt_cpu + /// The libipt cpu used when recording the trace. + ThreadTraceDecoder(const std::shared_ptr &trace_thread, + const pt_cpu &pt_cpu) + : m_trace_thread(trace_thread), m_pt_cpu(pt_cpu), m_decoded_thread() {} + + /// Decode the thread and store the result internally. + /// + /// \return + /// A \a DecodedThread instance. + const DecodedThread &Decode(); + +private: + ThreadTraceDecoder(const ThreadTraceDecoder &other) = delete; + ThreadTraceDecoder &operator=(const ThreadTraceDecoder &other) = delete; + + std::shared_ptr m_trace_thread; + pt_cpu m_pt_cpu; + llvm::Optional m_decoded_thread; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -0,0 +1,215 @@ +//===-- IntelPTDecoder.cpp --------------------------------------*- C++ -*-===// +// +// 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 "IntelPTDecoder.h" + +#include "llvm/Support/MemoryBuffer.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadTrace.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +/// Move the decoder forward to the next synchronization point (i.e. next PSB +/// packet). +/// +/// Once the decoder is at that sync. point, it can start decoding instructions. +/// +/// \return +/// A negative number with the libipt error if we couldn't synchronize. +/// Otherwise, a positive number with the synchronization status will be +/// returned. +static int FindNextSynchronizationPoint(pt_insn_decoder &decoder) { + // Try to sync the decoder. If it fails, then get + // the decoder_offset and try to sync again from + // the next synchronization point. If the + // new_decoder_offset is same as decoder_offset + // then we can't move to the next synchronization + // point. Otherwise, keep resyncing until either + // end of trace stream (eos) is reached or + // pt_insn_sync_forward() passes. + int errcode = pt_insn_sync_forward(&decoder); + + if (errcode != -pte_eos && errcode < 0) { + uint64_t decoder_offset = 0; + int errcode_off = pt_insn_get_offset(&decoder, &decoder_offset); + if (errcode_off >= 0) { // we could get the offset + while (true) { + errcode = pt_insn_sync_forward(&decoder); + if (errcode >= 0 || errcode == -pte_eos) + break; + + uint64_t new_decoder_offset = 0; + errcode_off = pt_insn_get_offset(&decoder, &new_decoder_offset); + if (errcode_off < 0) + break; // We can't further synchronize. + else if (new_decoder_offset <= decoder_offset) { + // We tried resyncing the decoder and + // decoder didn't make any progress because + // the offset didn't change. We will not + // make any progress further. Hence, + // stopping in this situation. + break; + } + // We'll try again starting from a new offset. + decoder_offset = new_decoder_offset; + } + } + } + + return errcode; +} + +/// Before querying instructions, we need to query the events associated that +/// instruction e.g. timing events like ptev_tick, or paging events like +/// ptev_paging. +/// +/// \return +/// 0 if there were no errors processing the events, or a negative libipt +/// error code in case of errors. +static int ProcessPTEvents(pt_insn_decoder &decoder, int errcode) { + while (errcode & pts_event_pending) { + pt_event event; + errcode = pt_insn_event(&decoder, &event, sizeof(event)); + if (errcode < 0) + return errcode; + } + return 0; +}; + +/// 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 +/// but with some relaxation to allow for gaps in the trace. +/// +/// Error codes returned by libipt while decoding are: +/// - negative: actual errors +/// - positive or zero: not an error, but a list of bits signaling the status of +/// the decoder +/// +/// \param[in] decoder +/// A configured libipt \a pt_insn_decoder. +/// +/// \return +/// The decoded instructions. +static std::vector +DecodeInstructions(pt_insn_decoder &decoder) { + std::vector instructions; + + while (true) { + int errcode = FindNextSynchronizationPoint(decoder); + if (errcode == -pte_eos) + break; + + if (errcode < 0) { + instructions.emplace_back(make_error(errcode)); + break; + } + + // We have synchronized, so we can start decoding + // instructions and events. + while (true) { + errcode = ProcessPTEvents(decoder, errcode); + if (errcode < 0) { + instructions.emplace_back(make_error(errcode)); + break; + } + pt_insn insn; + + errcode = pt_insn_next(&decoder, &insn, sizeof(insn)); + if (errcode == -pte_eos) + break; + + if (errcode < 0) { + instructions.emplace_back(make_error(errcode, insn.ip)); + break; + } + + instructions.emplace_back(insn); + } + } + + return instructions; +} + +/// Callback used by libipt for reading the process memory. +/// +/// More information can be found in +/// https://github.com/intel/libipt/blob/master/doc/man/pt_image_set_callback.3.md +static int ReadProcessMemory(uint8_t *buffer, size_t size, + const pt_asid * /* unused */, uint64_t pc, + void *context) { + Process *process = static_cast(context); + + Status error; + int bytes_read = process->ReadMemory(pc, buffer, size, error); + if (error.Fail()) + return -pte_nomap; + return bytes_read; +} + +static std::vector makeInstructionListFromError(Error err) { + std::vector instructions; + instructions.emplace_back(std::move(err)); + return instructions; +} + +static std::vector +CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, + const FileSpec &trace_file) { + ErrorOr> trace_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = trace_or_error.getError()) + return makeInstructionListFromError(errorCodeToError(err)); + + MemoryBuffer &trace = **trace_or_error; + + pt_config config; + pt_config_init(&config); + config.cpu = pt_cpu; + + if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) + return makeInstructionListFromError(make_error(errcode)); + + // The libipt library does not modify the trace buffer, hence the following + // cast is safe. + config.begin = + reinterpret_cast(const_cast(trace.getBufferStart())); + config.end = + reinterpret_cast(const_cast(trace.getBufferEnd())); + + pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); + if (!decoder) + return makeInstructionListFromError(make_error(-pte_nomem)); + + pt_image *image = pt_insn_get_image(decoder); + + int errcode = pt_image_set_callback(image, ReadProcessMemory, &process); + assert(errcode == 0); + (void)errcode; + + std::vector instructions = DecodeInstructions(*decoder); + + pt_insn_free_decoder(decoder); + return instructions; +} + +const DecodedThread &ThreadTraceDecoder::Decode() { + if (!m_decoded_thread.hasValue()) { + m_decoded_thread = DecodedThread( + CreateDecoderAndDecode(*m_trace_thread->GetProcess(), m_pt_cpu, + m_trace_thread->GetTraceFile())); + } + + return *m_decoded_thread; +} 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 @@ -9,12 +9,8 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H -#include "intel-pt.h" -#include "llvm/ADT/Optional.h" - +#include "IntelPTDecoder.h" #include "TraceIntelPTSessionFileParser.h" -#include "lldb/Target/Trace.h" -#include "lldb/lldb-private.h" namespace lldb_private { namespace trace_intel_pt { @@ -59,6 +55,15 @@ llvm::StringRef GetSchema() override; + void TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function load_addr)> + callback) override; + + size_t GetInstructionCount(const Thread &thread) override; + + size_t GetCursorPosition(const Thread &thread) override; + private: friend class TraceIntelPTSessionFileParser; @@ -68,8 +73,20 @@ TraceIntelPT(const pt_cpu &pt_cpu, const std::vector> &traced_threads); + /// Decode the trace of the given thread that, i.e. recontruct the traced + /// instructions. That trace must be managed by this class. + /// + /// \param[in] thread + /// If \a thread is a \a ThreadTrace, then its internal trace file will be + /// decoded. Live threads are not currently supported. + /// + /// \return + /// A \a DecodedThread instance if decoding was successful, or a \b + /// nullptr if the thread's trace is not managed by this class. + const DecodedThread *Decode(const Thread &thread); + pt_cpu m_pt_cpu; - std::map, std::shared_ptr> + std::map, ThreadTraceDecoder> m_trace_threads; }; 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 @@ -64,5 +64,46 @@ : m_pt_cpu(pt_cpu) { for (const std::shared_ptr &thread : traced_threads) m_trace_threads.emplace( - std::make_pair(thread->GetProcess()->GetID(), thread->GetID()), thread); + std::piecewise_construct, + std::forward_as_tuple(thread->GetProcess()->GetID(), thread->GetID()), + std::forward_as_tuple(thread, pt_cpu)); +} + +const DecodedThread *TraceIntelPT::Decode(const Thread &thread) { + auto it = m_trace_threads.find( + std::make_pair(thread.GetProcess()->GetID(), thread.GetID())); + if (it == m_trace_threads.end()) + return nullptr; + return &it->second.Decode(); +} + +size_t TraceIntelPT::GetCursorPosition(const Thread &thread) { + const DecodedThread *decoded_thread = Decode(thread); + if (!decoded_thread) + return 0; + return decoded_thread->GetCursorPosition(); +} + +void TraceIntelPT::TraverseInstructions( + const Thread &thread, size_t position, TraceDirection direction, + std::function load_addr)> + callback) { + const DecodedThread *decoded_thread = Decode(thread); + if (!decoded_thread) + return; + + 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())) + break; +} + +size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { + if (const DecodedThread *decoded_thread = Decode(thread)) + return decoded_thread->GetInstructions().size(); + else + return 0; } diff --git a/lldb/source/Symbol/SymbolContext.cpp b/lldb/source/Symbol/SymbolContext.cpp --- a/lldb/source/Symbol/SymbolContext.cpp +++ b/lldb/source/Symbol/SymbolContext.cpp @@ -71,7 +71,8 @@ const Address &addr, bool show_fullpaths, bool show_module, bool show_inlined_frames, bool show_function_arguments, - bool show_function_name) const { + bool show_function_name, + bool show_inline_callsite_line_info) const { bool dumped_something = false; if (show_module && module_sp) { if (show_fullpaths) @@ -127,11 +128,17 @@ s->Printf(" + %" PRIu64, inlined_function_offset); } } - const Declaration &call_site = inlined_block_info->GetCallSite(); - if (call_site.IsValid()) { + if (show_inline_callsite_line_info) { + const Declaration &call_site = inlined_block_info->GetCallSite(); + if (call_site.IsValid()) { + s->PutCString(" at "); + call_site.DumpStopContext(s, show_fullpaths); + } + } else if (line_entry.IsValid()) { s->PutCString(" at "); - call_site.DumpStopContext(s, show_fullpaths); + line_entry.DumpStopContext(s, show_fullpaths); } + if (show_inlined_frames) { s->EOL(); s->Indent(); diff --git a/lldb/source/Target/ProcessTrace.cpp b/lldb/source/Target/ProcessTrace.cpp --- a/lldb/source/Target/ProcessTrace.cpp +++ b/lldb/source/Target/ProcessTrace.cpp @@ -12,6 +12,8 @@ #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" using namespace lldb; @@ -121,5 +123,9 @@ size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size, Status &error) { - return 0; + Address resolved_address; + GetTarget().GetSectionLoadList().ResolveLoadAddress(addr, resolved_address); + + return GetTarget().ReadMemoryFromFileCache(resolved_address, buf, size, + error); } 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 @@ -8,11 +8,13 @@ #include "lldb/Target/Trace.h" -#include - #include "llvm/Support/Format.h" +#include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/Stream.h" @@ -79,10 +81,188 @@ return createInvalidPlugInError(name); } +static int GetNumberOfDigits(size_t num) { + return num == 0 ? 1 : static_cast(log10(num)) + 1; +} + +/// 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 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; + + s.Printf(" "); + + if (!sc.module_sp) + s.Printf("(none)"); + else if (!sc.function && !sc.symbol) + s.Printf("%s`(none)", + 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, + /*show_function_name*/ true, + /*show_inline_callsite_line_info*/ false); + 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; +} + void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const { - s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = 1000\n", - thread.GetIndexID(), thread.GetID()); - s.Printf(" would print %zu instructions from position %zu\n", count, - start_position); + size_t end_position, bool raw) { + size_t instructions_count = GetInstructionCount(thread); + s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", + thread.GetIndexID(), thread.GetID(), instructions_count); + + if (count == 0 || end_position >= instructions_count) + return; + + 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); + + TraverseInstructions( + 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 + 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); + } + + printInstructionIndex(index); + s.Printf("0x%016" PRIx64 " ", *load_address); + + if (!raw) { + disassembler = + DumpInstructionInfo(s, sc, disassembler, exe_ctx, address); + } + + 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; + }); } diff --git a/lldb/source/Target/TraceSessionFileParser.cpp b/lldb/source/Target/TraceSessionFileParser.cpp --- a/lldb/source/Target/TraceSessionFileParser.cpp +++ b/lldb/source/Target/TraceSessionFileParser.cpp @@ -37,7 +37,6 @@ ModuleSpec module_spec; module_spec.GetFileSpec() = local_file_spec; module_spec.GetPlatformFileSpec() = system_file_spec; - module_spec.SetObjectOffset(module.load_address.value); if (module.uuid.hasValue()) module_spec.GetUUID().SetFromStringRef(*module.uuid); @@ -45,7 +44,14 @@ Status error; ModuleSP module_sp = target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error); - return error.ToError(); + + if (error.Fail()) + return error.ToError(); + + bool load_addr_changed = false; + module_sp->SetLoadAddress(*target_sp, module.load_address.value, false, + load_addr_changed); + return llvm::Error::success(); } Error TraceSessionFileParser::CreateJSONError(json::Path::Root &root, diff --git a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py --- a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py +++ b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -20,7 +20,8 @@ error=True) # We now check the output when there's a non-running target - self.expect("target create " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("target create " + + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) self.expect("thread trace dump instructions", substrs=["error: invalid process"], @@ -34,59 +35,225 @@ substrs=["error: this thread is not being traced"], error=True) - def testDumpInstructions(self): - self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + def testRawDumpInstructions(self): + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), substrs=["intel-pt"]) - self.expect("thread trace dump instructions", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 20 instructions from position 0']) + self.expect("thread trace dump instructions --raw", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [ 1] 0x0000000000400518 + [ 2] 0x000000000040051f + [ 3] 0x0000000000400529 + [ 4] 0x000000000040052d + [ 5] 0x0000000000400521 + [ 6] 0x0000000000400525 + [ 7] 0x0000000000400529 + [ 8] 0x000000000040052d + [ 9] 0x0000000000400521 + [10] 0x0000000000400525 + [11] 0x0000000000400529 + [12] 0x000000000040052d + [13] 0x0000000000400521 + [14] 0x0000000000400525 + [15] 0x0000000000400529 + [16] 0x000000000040052d + [17] 0x0000000000400521 + [18] 0x0000000000400525 + [19] 0x0000000000400529 + [20] 0x000000000040052d''']) - # We check if we can pass count and offset - self.expect("thread trace dump instructions --count 5 --start-position 10", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 5 instructions from position 10']) + # We check if we can pass count and position + self.expect("thread trace dump instructions --count 5 --position 10 --raw", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [ 6] 0x0000000000400525 + [ 7] 0x0000000000400529 + [ 8] 0x000000000040052d + [ 9] 0x0000000000400521 + [10] 0x0000000000400525''']) # We check if we can access the thread by index id - self.expect("thread trace dump instructions 1", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 20 instructions from position 0']) + self.expect("thread trace dump instructions 1 --raw", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + [ 1] 0x0000000000400518''']) # We check that we get an error when using an invalid thread index id self.expect("thread trace dump instructions 10", error=True, substrs=['error: no thread with index: "10"']) - def testDumpInstructionsWithMultipleThreads(self): + def testDumpFullInstructionsWithMultipleThreads(self): # We load a trace with two threads - self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json")) + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json")) # We print the instructions of two threads simultaneously - self.expect("thread trace dump instructions 1 2", - substrs=['''thread #1: tid = 3842849, total instructions = 1000 - would print 20 instructions from position 0 -thread #2: tid = 3842850, total instructions = 1000 - would print 20 instructions from position 0''']) - - # We use custom --count and --start-position, saving the command to history for later - ci = self.dbg.GetCommandInterpreter() - - result = lldb.SBCommandReturnObject() - ci.HandleCommand("thread trace dump instructions 1 2 --count 12 --start-position 5", result, True) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 5 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 5''', result.GetOutput()) - - # We use a repeat command and ensure the previous count is used and the start-position has moved to the next position - result = lldb.SBCommandReturnObject() - ci.HandleCommand("", result) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 17 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 17''', result.GetOutput()) - - ci.HandleCommand("", result) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 29 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 29''', result.GetOutput()) + self.expect("thread trace dump instructions 1 2 --count 2", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + a.out`main + 28 at main.cpp:4 + [19] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) + [20] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 +thread #2: tid = 3842850, total instructions = 21 + a.out`main + 28 at main.cpp:4 + [19] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) + [20] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5''']) + + # We use custom --count and --position, saving the command to history for later + self.expect("thread trace dump instructions 1 2 --count 2 --position 20", inHistory=True, + substrs=['''thread #1: tid = 3842849, total instructions = 21 + a.out`main + 28 at main.cpp:4 + [19] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) + [20] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 +thread #2: tid = 3842850, total instructions = 21 + a.out`main + 28 at main.cpp:4 + [19] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) + [20] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5''']) + + # We use a repeat command twice and ensure the previous count is used and the + # start position moves with each command. + self.expect("", inHistory=True, + substrs=['''thread #1: tid = 3842849, total instructions = 21 + a.out`main + 20 at main.cpp:5 + [17] 0x0000000000400521 xorl $0x1, -0x4(%rbp) + a.out`main + 24 at main.cpp:4 + [18] 0x0000000000400525 addl $0x1, -0x8(%rbp) +thread #2: tid = 3842850, total instructions = 21 + a.out`main + 20 at main.cpp:5 + [17] 0x0000000000400521 xorl $0x1, -0x4(%rbp) + a.out`main + 24 at main.cpp:4 + [18] 0x0000000000400525 addl $0x1, -0x8(%rbp)''']) + + self.expect("", inHistory=True, + substrs=['''thread #1: tid = 3842849, total instructions = 21 + a.out`main + 28 at main.cpp:4 + [15] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) + [16] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 +thread #2: tid = 3842850, total instructions = 21 + a.out`main + 28 at main.cpp:4 + [15] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) + [16] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5''']) + + def testInvalidBounds(self): + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json")) + + # The output should be work when too many instructions are asked + self.expect("thread trace dump instructions --count 20 --position 2", + substrs=['''thread #1: tid = 3842849, total instructions = 21 + a.out`main + 4 at main.cpp:2 + [0] 0x0000000000400511 movl $0x0, -0x4(%rbp) + a.out`main + 11 at main.cpp:4 + [1] 0x0000000000400518 movl $0x0, -0x8(%rbp) + [2] 0x000000000040051f jmp 0x400529 ; <+28> at main.cpp:4''']) + + # Should print no instructions if the position is out of bounds + self.expect("thread trace dump instructions --position 23", + endstr='thread #1: tid = 3842849, total instructions = 21\n') + + # Should fail with negative bounds + self.expect("thread trace dump instructions --position -1", error=True) + self.expect("thread trace dump instructions --count -1", error=True) + + def testWrongImage(self): + self.expect("trace load " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json")) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 2 + [0] 0x0000000000400511 error: no memory mapped at this address + [1] 0x0000000000400518 error: no memory mapped at this address''']) + + def testWrongCPU(self): + self.expect("trace load " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json")) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 1 + [0] error: unknown cpu''']) + + def testMultiFileTraceWithMissingModule(self): + self.expect("trace load " + + os.path.join(self.getSourceDir(), "intelpt-trace-multi-file", "multi-file-no-ld.json")) + + # This instructions in this test covers the following flow: + # + # - The trace starts with a call to libfoo, which triggers the dynamic + # linker, but the dynamic linker is not included in the JSON file, + # thus the trace reports a set of missing instructions after + # instruction [6]. + # - Then, the dump continues in the next synchronization point showing + # a call to an inlined function, which is displayed as [inlined]. + # - Finally, a call to libfoo is performed, which invokes libbar inside. + # + # Whenever there's a line or symbol change, including the inline case, a + # line is printed showing the symbol context change. + # + # Finally, the instruction disassembly is included in the dump. + self.expect("thread trace dump instructions --count 50", + substrs=['''thread #1: tid = 815455, total instructions = 46 + a.out`main + 15 at main.cpp:10 + [ 0] 0x000000000040066f callq 0x400540 ; symbol stub for: foo() + a.out`symbol stub for: foo() + [ 1] 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 + [ 2] 0x0000000000400546 pushq $0x2 + [ 3] 0x000000000040054b jmp 0x400510 + a.out`(none) + [ 4] 0x0000000000400510 pushq 0x200af2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 8 + [ 5] 0x0000000000400516 jmpq *0x200af4(%rip) ; _GLOBAL_OFFSET_TABLE_ + 16 + [ 6] 0x00007ffff7df1950 error: no memory mapped at this address + ...missing instructions + a.out`main + 20 at main.cpp:10 + [ 7] 0x0000000000400674 movl %eax, -0xc(%rbp) + a.out`main + 23 at main.cpp:12 + [ 8] 0x0000000000400677 movl -0xc(%rbp), %eax + [ 9] 0x000000000040067a addl $0x1, %eax + [10] 0x000000000040067f movl %eax, -0xc(%rbp) + a.out`main + 34 [inlined] inline_function() at main.cpp:4 + [11] 0x0000000000400682 movl $0x0, -0x4(%rbp) + a.out`main + 41 [inlined] inline_function() + 7 at main.cpp:5 + [12] 0x0000000000400689 movl -0x4(%rbp), %eax + [13] 0x000000000040068c addl $0x1, %eax + [14] 0x0000000000400691 movl %eax, -0x4(%rbp) + a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6 + [15] 0x0000000000400694 movl -0x4(%rbp), %eax + a.out`main + 55 at main.cpp:14 + [16] 0x0000000000400697 movl -0xc(%rbp), %ecx + [17] 0x000000000040069a addl %eax, %ecx + [18] 0x000000000040069c movl %ecx, -0xc(%rbp) + a.out`main + 63 at main.cpp:16 + [19] 0x000000000040069f callq 0x400540 ; symbol stub for: foo() + a.out`symbol stub for: foo() + [20] 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 + libfoo.so`foo() at foo.cpp:3 + [21] 0x00007ffff7bd96e0 pushq %rbp + [22] 0x00007ffff7bd96e1 movq %rsp, %rbp + libfoo.so`foo() + 4 at foo.cpp:4 + [23] 0x00007ffff7bd96e4 subq $0x10, %rsp + [24] 0x00007ffff7bd96e8 callq 0x7ffff7bd95d0 ; symbol stub for: bar() + libfoo.so`symbol stub for: bar() + [25] 0x00007ffff7bd95d0 jmpq *0x200a4a(%rip) ; _GLOBAL_OFFSET_TABLE_ + 32 + libbar.so`bar() at bar.cpp:1 + [26] 0x00007ffff79d7690 pushq %rbp + [27] 0x00007ffff79d7691 movq %rsp, %rbp + libbar.so`bar() + 4 at bar.cpp:2 + [28] 0x00007ffff79d7694 movl $0x1, -0x4(%rbp) + libbar.so`bar() + 11 at bar.cpp:3 + [29] 0x00007ffff79d769b movl -0x4(%rbp), %eax + [30] 0x00007ffff79d769e addl $0x1, %eax + [31] 0x00007ffff79d76a3 movl %eax, -0x4(%rbp) + libbar.so`bar() + 22 at bar.cpp:4 + [32] 0x00007ffff79d76a6 movl -0x4(%rbp), %eax + [33] 0x00007ffff79d76a9 popq %rbp + [34] 0x00007ffff79d76aa retq + libfoo.so`foo() + 13 at foo.cpp:4 + [35] 0x00007ffff7bd96ed movl %eax, -0x4(%rbp) + libfoo.so`foo() + 16 at foo.cpp:5 + [36] 0x00007ffff7bd96f0 movl -0x4(%rbp), %eax + [37] 0x00007ffff7bd96f3 addl $0x1, %eax + [38] 0x00007ffff7bd96f8 movl %eax, -0x4(%rbp) + libfoo.so`foo() + 27 at foo.cpp:6 + [39] 0x00007ffff7bd96fb movl -0x4(%rbp), %eax + [40] 0x00007ffff7bd96fe addq $0x10, %rsp + [41] 0x00007ffff7bd9702 popq %rbp + [42] 0x00007ffff7bd9703 retq + a.out`main + 68 at main.cpp:16 + [43] 0x00000000004006a4 movl -0xc(%rbp), %ecx + [44] 0x00000000004006a7 addl %eax, %ecx + [45] 0x00000000004006a9 movl %ecx, -0xc(%rbp)''']) diff --git a/lldb/test/API/commands/trace/intelpt-trace-multi-file/a.out b/lldb/test/API/commands/trace/intelpt-trace-multi-file/a.out new file mode 100755 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@