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 @@ -45,11 +45,6 @@ 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 @@ -137,62 +132,6 @@ /// The JSON schema of this Trace plug-in. virtual llvm::StringRef GetSchema() = 0; - /// Dump \a count instructions of the given thread's trace ending at the - /// given \a end_position position. - /// - /// 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. - /// - /// \param[in] s - /// The stream object where the instructions are printed. - /// - /// \param[in] count - /// The number of instructions 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 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( - Thread &thread, size_t position, TraceDirection direction, - std::function load_addr)> - callback) = 0; - /// Get a \a TraceCursor for the given thread's trace. /// /// \return @@ -201,16 +140,6 @@ /// trace. virtual lldb::TraceCursorUP GetCursor(Thread &thread) = 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, 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. /// /// \param[in] thread diff --git a/lldb/include/lldb/Target/TraceCursor.h b/lldb/include/lldb/Target/TraceCursor.h --- a/lldb/include/lldb/Target/TraceCursor.h +++ b/lldb/include/lldb/Target/TraceCursor.h @@ -11,6 +11,8 @@ #include "lldb/lldb-private.h" +#include "lldb/Target/ExecutionContext.h" + namespace lldb_private { /// Class used for iterating over the instructions of a thread's trace. @@ -36,74 +38,134 @@ /// A \a TraceCursor always points to a specific instruction or error in the /// trace. /// -/// The Trace initially points to the last item in the trace. +/// Defaults: +/// By default, the cursor points at the end item of the trace, moves +/// backwards, has a move granularity of \a +/// eTraceInstructionControlFlowTypeInstruction (i.e. visit every instruction) +/// and stops at every error (the "ignore errors" flag is \b false). See the +/// \a TraceCursor::Next() method for more documentation. /// /// Sample usage: /// /// TraceCursorUP cursor = trace.GetTrace(thread); /// -/// auto granularity = eTraceInstructionControlFlowTypeCall | -/// eTraceInstructionControlFlowTypeReturn; +/// cursor->SetGranularity(eTraceInstructionControlFlowTypeCall | +/// eTraceInstructionControlFlowTypeReturn); /// /// do { /// if (llvm::Error error = cursor->GetError()) /// cout << "error found at: " << llvm::toString(error) << endl; /// else if (cursor->GetInstructionControlFlowType() & -/// eTraceInstructionControlFlowTypeCall) +/// eTraceInstructionControlFlowTypeCall) /// std::cout << "call found at " << cursor->GetLoadAddress() << /// std::endl; /// else if (cursor->GetInstructionControlFlowType() & -/// eTraceInstructionControlFlowTypeReturn) +/// eTraceInstructionControlFlowTypeReturn) /// std::cout << "return found at " << cursor->GetLoadAddress() << /// std::endl; -/// } while(cursor->Prev(granularity)); +/// } while(cursor->Next()); +/// +/// Low level traversal: +/// Unlike the \a TraceCursor::Next() API, which uses a given granularity and +/// direction to advance the cursor, the \a TraceCursor::Seek() method can be +/// used to reposition the cursor to an offset of the end, beginning, or +/// current position of the trace. class TraceCursor { public: + /// Helper enum to indicate the reference point when invoking + /// \a TraceCursor::Seek(). + enum class SeekType { + /// The beginning of the trace, i.e the oldest item. + Set = 0, + /// The current position in the trace. + Current, + /// The end of the trace, i.e the most recent item. + End + }; + + /// Create a cursor that initially points to the end of the trace, i.e. the + /// most recent item. + TraceCursor(lldb::ThreadSP thread_sp); + virtual ~TraceCursor() = default; - /// Move the cursor to the next instruction more recent chronologically in the - /// trace given the provided granularity. If such instruction is not found, - /// the cursor doesn't move. + /// Set the granularity to use in the \a TraceCursor::Next() method. + void SetGranularity(lldb::TraceInstructionControlFlowType granularity); + + /// Set the "ignore errors" flag to use in the \a TraceCursor::Next() method. + void SetIgnoreErrors(bool ignore_errors); + + /// Set the direction to use in the \a TraceCursor::Next() method. + /// + /// \param[in] forwards + /// If \b true, then the traversal will be forwards, otherwise backwards. + void SetForwards(bool forwards); + + /// Check if the direction to use in the \a TraceCursor::Next() method is + /// forwards. + /// + /// \return + /// \b true if the current direction is forwards, \b false if backwards. + bool IsForwards() const; + + /// Move the cursor to the next instruction that matches the current + /// granularity. + /// + /// Direction: + /// The traversal is done following the current direction of the trace. If + /// it is forwards, the instructions are visited forwards + /// chronologically. Otherwise, the traversal is done in + /// the opposite direction. By default, a cursor moves backwards unless + /// changed with \a TraceCursor::SetForwards(). + /// + /// Granularity: + /// The cursor will traverse the trace looking for the first instruction + /// that matches the current granularity. If there aren't any matching + /// instructions, the cursor won't move, to give the opportunity of + /// changing granularities. + /// + /// Ignore errors: + /// If the "ignore errors" flags is \b false, the traversal will stop as + /// soon as it finds an error in the trace and the cursor will point at + /// it. + /// + /// \return + /// \b true if the cursor effectively moved, \b false otherwise. + virtual bool Next() = 0; + + /// Make the cursor point to an item in the trace based on an origin point and + /// an offset. This API doesn't distinguishes instruction types nor errors in + /// the trace, unlike the \a TraceCursor::Next() method. + /// + /// The resulting position of the trace is + /// origin + offset /// - /// \param[in] granularity - /// Bitmask granularity filter. The cursor stops at the next - /// instruction that matches the specified granularity. + /// If this resulting position would be out of bounds, it will be adjusted to + /// the last or first item in the trace correspondingly. /// - /// \param[in] ignore_errors - /// If \b false, the cursor stops as soon as it finds a failure in the - /// trace and points at it. + /// \param[in] offset + /// How many items to move forwards (if positive) or backwards (if + /// negative) from the given origin point. + /// + /// \param[in] origin + /// The reference point to use when moving the cursor. /// /// \return - /// \b true if the cursor effectively moved and now points to a different - /// item in the trace, including errors when \b ignore_errors is \b false. - /// In other words, if \b false is returned, then the trace is pointing at - /// the same item in the trace as before. - virtual bool Next(lldb::TraceInstructionControlFlowType granularity = - lldb::eTraceInstructionControlFlowTypeInstruction, - bool ignore_errors = false) = 0; - - /// Similar to \a TraceCursor::Next(), but moves backwards chronologically. - virtual bool Prev(lldb::TraceInstructionControlFlowType granularity = - lldb::eTraceInstructionControlFlowTypeInstruction, - bool ignore_errors = false) = 0; - - /// Force the cursor to point to the end of the trace, i.e. the most recent - /// item. - virtual void SeekToEnd() = 0; - - /// Force the cursor to point to the beginning of the trace, i.e. the oldest - /// item. - virtual void SeekToBegin() = 0; + /// The number of trace items moved from the origin. + virtual size_t Seek(ssize_t offset, SeekType origin) = 0; /// \return - /// \b true if the trace corresponds to a live process who has resumed after - /// the trace cursor was created. Otherwise, including the case in which the - /// process is a post-mortem one, return \b false. - bool IsStale(); + /// The \a ExecutionContextRef of the backing thread from the creation time + /// of this cursor. + ExecutionContextRef &GetExecutionContextRef(); /// Instruction or error information /// \{ + /// \return + /// Whether the cursor points to an error or not. + virtual bool IsError() = 0; + /// Get the corresponding error message if the cursor points to an error in /// the trace. /// @@ -124,14 +186,15 @@ /// to an error in the trace, return \b 0. virtual lldb::TraceInstructionControlFlowType GetInstructionControlFlowType() = 0; - /// \} -private: - /// The stop ID when the cursor was created. - uint32_t m_stop_id = 0; - /// The trace that owns this cursor. - lldb::TraceSP m_trace_sp; +protected: + ExecutionContextRef m_exe_ctx_ref; + + lldb::TraceInstructionControlFlowType m_granularity = + lldb::eTraceInstructionControlFlowTypeInstruction; + bool m_ignore_errors = false; + bool m_forwards = false; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/TraceInstructionDumper.h b/lldb/include/lldb/Target/TraceInstructionDumper.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/TraceInstructionDumper.h @@ -0,0 +1,72 @@ +//===-- TraceInstructionDumper.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 +// +//===----------------------------------------------------------------------===// + +#include "lldb/Target/TraceCursor.h" + +#ifndef LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H +#define LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H + +namespace lldb_private { + +/// Class used to dump the instructions of a \a TraceCursor using its current +/// state and granularity. +class TraceInstructionDumper { +public: + /// Create a instruction dumper for the cursor. + /// + /// \param[in] cursor + /// The cursor whose instructions will be dumped. + /// + /// \param[in] initial_index + /// Presentation index to use for referring to the current instruction + /// of the cursor. If the direction is forwards, the index will increase, + /// and if the direction is backwards, the index will decrease. + /// + /// \param[in] raw + /// Dump only instruction addresses without disassembly nor symbol + /// information. + TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, int initial_index = 0, + bool raw = false); + + /// Dump \a count instructions of the thread trace starting at the current + /// cursor position. + /// + /// This effectively moves the cursor to the next unvisited position, so that + /// a subsequent call to this method continues where it left off. + /// + /// \param[in] s + /// The stream object where the instructions are printed. + /// + /// \param[in] count + /// The number of instructions to print. + void DumpInstructions(Stream &s, size_t count); + + /// Indicate the dumper that no more data is available in the trace. + void SetNoMoreData(); + + /// \return + /// \b true if there's still more data to traverse in the trace. + bool HasMoreData(); + +private: + /// Move the cursor one step. + /// + /// \return + /// \b true if the cursor moved. + bool TryMoveOneStep(); + + lldb::TraceCursorUP m_cursor_up; + int m_index; + bool m_raw; + /// If \b true, all the instructions have been traversed. + bool m_no_more_data = false; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACE_INSTRUCTION_DUMPER_H 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 @@ -8,6 +8,7 @@ #include "CommandObjectThread.h" +#include #include #include "CommandObjectThreadUtil.h" @@ -32,6 +33,7 @@ #include "lldb/Target/ThreadPlan.h" #include "lldb/Target/ThreadPlanStepInRange.h" #include "lldb/Target/Trace.h" +#include "lldb/Target/TraceInstructionDumper.h" #include "lldb/Utility/State.h" using namespace lldb; @@ -2004,21 +2006,24 @@ m_count = count; break; } - case 'p': { - int32_t position; - if (option_arg.empty() || option_arg.getAsInteger(0, position) || - position < 0) + case 's': { + int32_t skip; + if (option_arg.empty() || option_arg.getAsInteger(0, skip) || skip < 0) error.SetErrorStringWithFormat( "invalid integer value for option '%s'", option_arg.str().c_str()); else - m_position = position; + m_skip = skip; break; } case 'r': { m_raw = true; break; } + case 'f': { + m_forwards = true; + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -2027,8 +2032,9 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_count = kDefaultCount; - m_position = llvm::None; + m_skip = 0; m_raw = false; + m_forwards = false; } llvm::ArrayRef GetDefinitions() override { @@ -2039,14 +2045,15 @@ // Instance variables to hold the values for command options. size_t m_count; - llvm::Optional m_position; + size_t m_skip; bool m_raw; + bool m_forwards; }; CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) : CommandObjectIterateOverThreads( interpreter, "thread trace dump instructions", - "Dump the traced instructions for one or more threads. If no " + "Dump the traced instructions for one or more threads. If no " "threads are specified, show the current thread. Use the " "thread-index \"all\" to see all threads.", nullptr, @@ -2063,14 +2070,14 @@ 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++; + if (!IsRepeatCommand()) + m_dumpers.clear(); + bool status = CommandObjectIterateOverThreads::DoExecute(args, result); m_create_repeat_command_just_invoked = false; @@ -2082,24 +2089,37 @@ } bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + Stream &s = result.GetOutputStream(); + const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); ThreadSP thread_sp = m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); - if (llvm::Optional insn_count = - trace_sp->GetInstructionCount(*thread_sp)) { - size_t count = m_options.m_count; - ssize_t position = - m_options.m_position.getValueOr((ssize_t)*insn_count - 1) - - m_consecutive_repetitions * count; - if (position < 0) - result.AppendError("error: no more data"); - else - trace_sp->DumpTraceInstructions(*thread_sp, result.GetOutputStream(), - count, position, m_options.m_raw); - } else { - result.AppendError("error: not traced"); + if (!m_dumpers.count(thread_sp->GetID())) { + lldb::TraceCursorUP cursor_up = trace_sp->GetCursor(*thread_sp); + // Set up the cursor and return the presentation index of the first + // instruction to dump after skipping instructions. + auto setUpCursor = [&]() { + cursor_up->SetForwards(m_options.m_forwards); + if (m_options.m_forwards) + return cursor_up->Seek(m_options.m_skip, TraceCursor::SeekType::Set); + return -cursor_up->Seek(-m_options.m_skip, TraceCursor::SeekType::End); + }; + + int initial_index = setUpCursor(); + + auto dumper = std::make_unique( + std::move(cursor_up), initial_index, m_options.m_raw); + + // This happens when the seek value was more than the number of available + // instructions. + if (std::abs(initial_index) < (int)m_options.m_skip) + dumper->SetNoMoreData(); + + m_dumpers[thread_sp->GetID()] = std::move(dumper); } + + m_dumpers[thread_sp->GetID()]->DumpInstructions(s, m_options.m_count); return true; } @@ -2108,7 +2128,7 @@ // Repeat command helpers std::string m_repeat_command; bool m_create_repeat_command_just_invoked; - size_t m_consecutive_repetitions = 0; + std::map> m_dumpers; }; // 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 @@ -1049,13 +1049,19 @@ } let Command = "thread trace dump instructions" in { + def thread_trace_dump_instructions_forwards: Option<"forwards", "f">, Group<1>, + Desc<"If specified, the trace is traversed forwards chronologically " + "starting at the oldest instruction. Otherwise, it starts at the most " + "recent one and the traversal is backwards.">; def thread_trace_dump_instructions_count : Option<"count", "c">, Group<1>, Arg<"Count">, - Desc<"The number of instructions to display ending at the current position.">; - def thread_trace_dump_instructions_position : Option<"position", "p">, + Desc<"The number of instructions to display starting at the most recent " + "instruction, or the oldest if --forwards is provided.">; + def thread_trace_dump_instructions_skip: Option<"skip", "s">, Group<1>, Arg<"Index">, - Desc<"The position to use instead of the current position of the trace.">; + Desc<"How many instruction to skip from the end of the trace to start " + "dumping instructions, or from the beginning if --forwards is provided">; def thread_trace_dump_instructions_raw : Option<"raw", "r">, Group<1>, Desc<"Dump only instruction address without disassembly nor symbol information.">; 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 @@ -17,6 +17,7 @@ CommandObjectTraceStartIntelPT.cpp DecodedThread.cpp IntelPTDecoder.cpp + TraceCursorIntelPT.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 --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -66,12 +66,7 @@ /// 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); - }); - } + IntelPTInstruction(llvm::Error err); /// Check if this object represents an error (i.e. a gap). /// @@ -80,15 +75,27 @@ bool IsError() const; /// \return - /// The instruction pointer address, or an \a llvm::Error if it is an - /// error. - llvm::Expected GetLoadAddress() const; + /// The instruction pointer address, or \a LLDB_INVALID_ADDRESS if it is + /// an error. + lldb::addr_t 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; + /// Get the \a lldb::TraceInstructionControlFlowType categories of the + /// instruction. + /// + /// \param[in] next_load_address + /// The address of the next instruction in the trace or \b + /// LLDB_INVALID_ADDRESS if not available. + /// + /// \return + /// The control flow categories, or \b 0 if the instruction is an error. + lldb::TraceInstructionControlFlowType + GetControlFlowType(lldb::addr_t next_load_address) const; + IntelPTInstruction(IntelPTInstruction &&other) = default; private: @@ -106,15 +113,14 @@ /// /// Each decoded thread contains a cursor to the current position the user is /// stopped at. See \a Trace::GetCursorPosition for more information. -class DecodedThread { +class DecodedThread : public std::enable_shared_from_this { public: - DecodedThread(std::vector &&instructions) - : m_instructions(std::move(instructions)), m_position(GetLastPosition()) { - } + DecodedThread(lldb::ThreadSP thread_sp, + std::vector &&instructions); /// Constructor with a single error signaling a complete failure of the /// decoding process. - DecodedThread(llvm::Error error); + DecodedThread(lldb::ThreadSP thread_sp, llvm::Error error); /// Get the instructions from the decoded trace. Some of them might indicate /// errors (i.e. gaps) in the trace. @@ -123,28 +129,16 @@ /// 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); - /// \} + /// Get a new cursor for the decoded thread. + lldb::TraceCursorUP GetCursor(); private: - /// \return - /// The index of the last element of the trace, or 0 if empty. - size_t GetLastPosition() const; - + lldb::ThreadSP m_thread_sp; std::vector m_instructions; - size_t m_position; }; +using DecodedThreadSP = std::shared_ptr; + } // namespace trace_intel_pt } // namespace lldb_private 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 @@ -8,8 +8,13 @@ #include "DecodedThread.h" +#include +#include + +#include "TraceCursorIntelPT.h" #include "lldb/Utility/StreamString.h" +using namespace lldb; using namespace lldb_private; using namespace lldb_private::trace_intel_pt; using namespace llvm; @@ -30,13 +35,18 @@ OS << "error: " << libipt_error_message; } +IntelPTInstruction::IntelPTInstruction(llvm::Error err) { + llvm::handleAllErrors(std::move(err), + [&](std::unique_ptr info) { + m_error = std::move(info); + }); + m_pt_insn.ip = LLDB_INVALID_ADDRESS; + m_pt_insn.iclass = ptic_error; +} + bool IntelPTInstruction::IsError() const { return (bool)m_error; } -Expected IntelPTInstruction::GetLoadAddress() const { - if (IsError()) - return ToError(); - return m_pt_insn.ip; -} +lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } Error IntelPTInstruction::ToError() const { if (!IsError()) @@ -48,22 +58,54 @@ m_error->convertToErrorCode()); } -size_t DecodedThread::GetLastPosition() const { - return m_instructions.empty() ? 0 : m_instructions.size() - 1; +TraceInstructionControlFlowType +IntelPTInstruction::GetControlFlowType(lldb::addr_t next_load_address) const { + if (IsError()) + return (TraceInstructionControlFlowType)0; + + TraceInstructionControlFlowType mask = + eTraceInstructionControlFlowTypeInstruction; + + switch (m_pt_insn.iclass) { + case ptic_cond_jump: + case ptic_jump: + case ptic_far_jump: + mask |= eTraceInstructionControlFlowTypeBranch; + if (m_pt_insn.ip + m_pt_insn.size != next_load_address) + mask |= eTraceInstructionControlFlowTypeTakenBranch; + break; + case ptic_return: + case ptic_far_return: + mask |= eTraceInstructionControlFlowTypeReturn; + break; + case ptic_call: + case ptic_far_call: + mask |= eTraceInstructionControlFlowTypeCall; + break; + default: + break; + } + + return mask; } ArrayRef DecodedThread::GetInstructions() const { return makeArrayRef(m_instructions); } -size_t DecodedThread::GetCursorPosition() const { return m_position; } +DecodedThread::DecodedThread(ThreadSP thread_sp, Error error) + : m_thread_sp(thread_sp) { + m_instructions.emplace_back(std::move(error)); +} -size_t DecodedThread::SetCursorPosition(size_t new_position) { - m_position = std::min(new_position, GetLastPosition()); - return m_position; +DecodedThread::DecodedThread(ThreadSP thread_sp, + std::vector &&instructions) + : m_thread_sp(thread_sp), m_instructions(std::move(instructions)) { + if (m_instructions.empty()) + m_instructions.emplace_back( + createStringError(inconvertibleErrorCode(), "empty trace")); } -DecodedThread::DecodedThread(Error error) { - m_instructions.emplace_back(std::move(error)); - m_position = GetLastPosition(); +lldb::TraceCursorUP DecodedThread::GetCursor() { + return std::make_unique(m_thread_sp, shared_from_this()); } diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -31,7 +31,7 @@ /// /// \return /// A \a DecodedThread instance. - const DecodedThread &Decode(); + DecodedThreadSP Decode(); ThreadDecoder(const ThreadDecoder &other) = delete; ThreadDecoder &operator=(const ThreadDecoder &other) = delete; @@ -41,9 +41,9 @@ /// /// \return /// A \a DecodedThread instance. - virtual DecodedThread DoDecode() = 0; + virtual DecodedThreadSP DoDecode() = 0; - llvm::Optional m_decoded_thread; + llvm::Optional m_decoded_thread; }; /// Decoder implementation for \a lldb_private::ThreadPostMortemTrace, which are @@ -59,7 +59,7 @@ TraceIntelPT &trace); private: - DecodedThread DoDecode() override; + DecodedThreadSP DoDecode() override; lldb::ThreadPostMortemTraceSP m_trace_thread; TraceIntelPT &m_trace; @@ -75,7 +75,7 @@ LiveThreadDecoder(Thread &thread, TraceIntelPT &trace); private: - DecodedThread DoDecode() override; + DecodedThreadSP DoDecode() override; lldb::ThreadSP m_thread_sp; TraceIntelPT &m_trace; 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 @@ -222,7 +222,7 @@ return cpu_info.takeError(); } -const DecodedThread &ThreadDecoder::Decode() { +DecodedThreadSP ThreadDecoder::Decode() { if (!m_decoded_thread.hasValue()) m_decoded_thread = DoDecode(); return *m_decoded_thread; @@ -232,22 +232,26 @@ const lldb::ThreadPostMortemTraceSP &trace_thread, TraceIntelPT &trace) : m_trace_thread(trace_thread), m_trace(trace) {} -DecodedThread PostMortemThreadDecoder::DoDecode() { +DecodedThreadSP PostMortemThreadDecoder::DoDecode() { if (Expected> instructions = DecodeTraceFile(*m_trace_thread->GetProcess(), m_trace, m_trace_thread->GetTraceFile())) - return DecodedThread(std::move(*instructions)); + return std::make_shared(m_trace_thread->shared_from_this(), + std::move(*instructions)); else - return DecodedThread(instructions.takeError()); + return std::make_shared(m_trace_thread->shared_from_this(), + instructions.takeError()); } LiveThreadDecoder::LiveThreadDecoder(Thread &thread, TraceIntelPT &trace) : m_thread_sp(thread.shared_from_this()), m_trace(trace) {} -DecodedThread LiveThreadDecoder::DoDecode() { +DecodedThreadSP LiveThreadDecoder::DoDecode() { if (Expected> instructions = DecodeLiveThread(*m_thread_sp, m_trace)) - return DecodedThread(std::move(*instructions)); + return std::make_shared(m_thread_sp, + std::move(*instructions)); else - return DecodedThread(instructions.takeError()); + return std::make_shared(m_thread_sp, + instructions.takeError()); } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -0,0 +1,48 @@ +//===-- TraceCursorIntelPT.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_TRACECURSORINTELPT_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACECURSORINTELPT_H + +#include "IntelPTDecoder.h" +#include "TraceIntelPTSessionFileParser.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceCursorIntelPT : public TraceCursor { +public: + TraceCursorIntelPT(lldb::ThreadSP thread_sp, + DecodedThreadSP decoded_thread_sp); + + size_t Seek(int64_t offset, SeekType origin) override; + + virtual bool Next() override; + + llvm::Error GetError() override; + + lldb::addr_t GetLoadAddress() override; + + lldb::TraceInstructionControlFlowType + GetInstructionControlFlowType() override; + + bool IsError() override; + +private: + size_t GetInternalInstructionSize(); + + /// Storage of the actual instructions + DecodedThreadSP m_decoded_thread_sp; + /// Internal instruction index currently pointing at. + size_t m_pos; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACECURSORINTELPT_H diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -0,0 +1,96 @@ +//===-- TraceCursorIntelPT.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 "TraceCursorIntelPT.h" +#include "DecodedThread.h" +#include "TraceIntelPT.h" + +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +TraceCursorIntelPT::TraceCursorIntelPT(ThreadSP thread_sp, + DecodedThreadSP decoded_thread_sp) + : TraceCursor(thread_sp), m_decoded_thread_sp(decoded_thread_sp) { + assert(!m_decoded_thread_sp->GetInstructions().empty() && + "a trace should have at least one instruction or error"); + m_pos = m_decoded_thread_sp->GetInstructions().size() - 1; +} + +size_t TraceCursorIntelPT::GetInternalInstructionSize() { + return m_decoded_thread_sp->GetInstructions().size(); +} + +bool TraceCursorIntelPT::Next() { + auto canMoveOne = [&]() { + if (IsForwards()) + return m_pos + 1 < GetInternalInstructionSize(); + return m_pos > 0; + }; + + size_t initial_pos = m_pos; + + while (canMoveOne()) { + m_pos += IsForwards() ? 1 : -1; + if (!m_ignore_errors && IsError()) + return true; + if (GetInstructionControlFlowType() & m_granularity) + return true; + } + + // Didn't find any matching instructions + m_pos = initial_pos; + return false; +} + +size_t TraceCursorIntelPT::Seek(int64_t offset, SeekType origin) { + int64_t last_index = GetInternalInstructionSize() - 1; + + auto fitPosToBounds = [&](int64_t raw_pos) -> int64_t { + return std::min(std::max((int64_t)0, raw_pos), last_index); + }; + + switch (origin) { + case TraceCursor::SeekType::Set: + m_pos = fitPosToBounds(offset); + return m_pos; + case TraceCursor::SeekType::End: + m_pos = fitPosToBounds(offset + last_index); + return last_index - m_pos; + case TraceCursor::SeekType::Current: + int64_t new_pos = fitPosToBounds(offset + m_pos); + int64_t dist = m_pos - new_pos; + m_pos = new_pos; + return std::abs(dist); + } +} + +bool TraceCursorIntelPT::IsError() { + return m_decoded_thread_sp->GetInstructions()[m_pos].IsError(); +} + +Error TraceCursorIntelPT::GetError() { + return m_decoded_thread_sp->GetInstructions()[m_pos].ToError(); +} + +lldb::addr_t TraceCursorIntelPT::GetLoadAddress() { + return m_decoded_thread_sp->GetInstructions()[m_pos].GetLoadAddress(); +} + +TraceInstructionControlFlowType +TraceCursorIntelPT::GetInstructionControlFlowType() { + lldb::addr_t next_load_address = + m_pos + 1 < GetInternalInstructionSize() + ? m_decoded_thread_sp->GetInstructions()[m_pos + 1].GetLoadAddress() + : LLDB_INVALID_ADDRESS; + return m_decoded_thread_sp->GetInstructions()[m_pos].GetControlFlowType( + next_load_address); +} 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 @@ -65,13 +65,6 @@ llvm::StringRef GetSchema() override; - void TraverseInstructions( - Thread &thread, size_t position, TraceDirection direction, - std::function load_addr)> - callback) override; - - llvm::Optional GetInstructionCount(Thread &thread) override; - lldb::TraceCursorUP GetCursor(Thread &thread) override; void DoRefreshLiveProcessState( @@ -145,24 +138,23 @@ : Trace(live_process), m_thread_decoders(){}; /// Decode the trace of the given thread that, i.e. recontruct the traced - /// instructions. That trace must be managed by this class. + /// instructions. /// /// \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(Thread &thread); + /// A \a DecodedThread shared pointer with the decoded instructions. Any + /// errors are embedded in the instruction list. + DecodedThreadSP Decode(Thread &thread); /// It is provided by either a session file or a live process' "cpuInfo" /// binary data. llvm::Optional m_cpu_info; std::map> m_thread_decoders; - /// Dummy DecodedThread used when decoding threads after there were errors - /// when refreshing the live process state. - llvm::Optional m_failed_live_threads_decoder; + /// Error gotten after a failed live process update, if any. + llvm::Optional m_live_refresh_error; }; } // namespace trace_intel_pt 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 @@ -9,6 +9,7 @@ #include "TraceIntelPT.h" #include "CommandObjectTraceStartIntelPT.h" +#include "DecodedThread.h" #include "TraceIntelPTConstants.h" #include "TraceIntelPTSessionFileParser.h" #include "lldb/Core/PluginManager.h" @@ -88,44 +89,23 @@ thread.get(), std::make_unique(thread, *this)); } -const DecodedThread *TraceIntelPT::Decode(Thread &thread) { +DecodedThreadSP TraceIntelPT::Decode(Thread &thread) { RefreshLiveProcessState(); - if (m_failed_live_threads_decoder.hasValue()) - return &*m_failed_live_threads_decoder; + if (m_live_refresh_error.hasValue()) + return std::make_shared( + thread.shared_from_this(), + createStringError(inconvertibleErrorCode(), *m_live_refresh_error)); auto it = m_thread_decoders.find(&thread); if (it == m_thread_decoders.end()) - return nullptr; - return &it->second->Decode(); + return std::make_shared( + thread.shared_from_this(), + createStringError(inconvertibleErrorCode(), "thread not traced")); + return it->second->Decode(); } lldb::TraceCursorUP TraceIntelPT::GetCursor(Thread &thread) { - // TODO: to implement - return nullptr; -} - -void TraceIntelPT::TraverseInstructions( - 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; -} - -Optional TraceIntelPT::GetInstructionCount(Thread &thread) { - if (const DecodedThread *decoded_thread = Decode(thread)) - return decoded_thread->GetInstructions().size(); - else - return None; + return Decode(thread)->GetCursor(); } Expected TraceIntelPT::GetCPUInfoForLiveProcess() { @@ -195,7 +175,7 @@ m_thread_decoders.clear(); if (!state) { - m_failed_live_threads_decoder = DecodedThread(state.takeError()); + m_live_refresh_error = toString(state.takeError()); return; } diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -69,6 +69,7 @@ ThreadPostMortemTrace.cpp Trace.cpp TraceCursor.cpp + TraceInstructionDumper.cpp TraceSessionFileParser.cpp UnixSignals.cpp UnwindAssembly.cpp 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 @@ -98,264 +98,6 @@ return createInvalidPlugInError(name); } -static int GetNumberOfDigits(size_t num) { - return num == 0 ? 1 : static_cast(log10(num)) + 1; -} - -/// \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; -} - -// 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; -} - -/// 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; -}; - -/// 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. -static void TraverseInstructionsWithSymbolInfo( - Trace &trace, Thread &thread, size_t position, - Trace::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.Contains(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)) { - 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()); - }; - - trace.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; - insn.address.SetLoadAddress(*load_address, &target); - 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); - }); -} - -/// 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 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 Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t end_position, bool raw) { - Optional instructions_count = GetInstructionCount(thread); - if (!instructions_count) { - s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), - thread.GetID()); - return; - } - - s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", - thread.GetIndexID(), thread.GetID(), *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; - auto printInstructionIndex = [&](size_t index) { - s.Printf(" [%*zu] ", digits_count, index); - }; - - bool was_prev_instruction_an_error = false; - Optional prev_insn; - - TraverseInstructionsWithSymbolInfo( - *this, thread, start_position, TraceDirection::Forwards, - 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"); - - if (!raw) - DumpInstructionSymbolContext(s, prev_insn, *insn); - - printInstructionIndex(index); - s.Printf("0x%016" PRIx64, insn->load_address); - - if (!raw) - DumpInstructionDisassembly(s, *insn); - - prev_insn = *insn; - was_prev_instruction_an_error = false; - } - - s.Printf("\n"); - return index < end_position; - }); -} - Error Trace::Start(const llvm::json::Value &request) { if (!m_live_process) return createStringError(inconvertibleErrorCode(), diff --git a/lldb/source/Target/TraceCursor.cpp b/lldb/source/Target/TraceCursor.cpp --- a/lldb/source/Target/TraceCursor.cpp +++ b/lldb/source/Target/TraceCursor.cpp @@ -8,8 +8,28 @@ #include "lldb/Target/TraceCursor.h" -#include "lldb/Target/Trace.h" +#include "lldb/Target/ExecutionContext.h" +using namespace lldb; using namespace lldb_private; +using namespace llvm; -bool TraceCursor::IsStale() { return m_stop_id != m_trace_sp->GetStopID(); } +TraceCursor::TraceCursor(lldb::ThreadSP thread_sp) + : m_exe_ctx_ref(ExecutionContext(thread_sp)) {} + +ExecutionContextRef &TraceCursor::GetExecutionContextRef() { + return m_exe_ctx_ref; +} + +void TraceCursor::SetGranularity( + lldb::TraceInstructionControlFlowType granularity) { + m_granularity = granularity; +} + +void TraceCursor::SetIgnoreErrors(bool ignore_errors) { + m_ignore_errors = ignore_errors; +} + +void TraceCursor::SetForwards(bool forwards) { m_forwards = forwards; } + +bool TraceCursor::IsForwards() const { return m_forwards; } diff --git a/lldb/source/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Target/TraceInstructionDumper.cpp @@ -0,0 +1,279 @@ +//===-- 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) + : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw) {} + +/// \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(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 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); + }; + + 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(); + } +} 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 @@ -35,8 +35,9 @@ os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), substrs=["intel-pt"]) - self.expect("thread trace dump instructions --raw", - substrs=['''thread #1: tid = 3842849, total instructions = 21 + self.expect("thread trace dump instructions --raw --count 21 --forwards", + substrs=['''thread #1: tid = 3842849 + [ 0] 0x0000000000400511 [ 1] 0x0000000000400518 [ 2] 0x000000000040051f [ 3] 0x0000000000400529 @@ -58,19 +59,27 @@ [19] 0x0000000000400529 [20] 0x000000000040052d''']) - # 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 + # We check if we can pass count and skip + self.expect("thread trace dump instructions --count 5 --skip 6 --raw --forwards", + substrs=['''thread #1: tid = 3842849 [ 6] 0x0000000000400525 [ 7] 0x0000000000400529 [ 8] 0x000000000040052d [ 9] 0x0000000000400521 [10] 0x0000000000400525''']) + self.expect("thread trace dump instructions --count 5 --skip 6 --raw", + substrs=['''thread #1: tid = 3842849 + [ -6] 0x0000000000400525 + [ -7] 0x0000000000400521 + [ -8] 0x000000000040052d + [ -9] 0x0000000000400529 + [-10] 0x0000000000400525''']) + # We check if we can access the thread by index id self.expect("thread trace dump instructions 1 --raw", - substrs=['''thread #1: tid = 3842849, total instructions = 21 - [ 1] 0x0000000000400518''']) + substrs=['''thread #1: tid = 3842849 + [ 0] 0x000000000040052d''']) # We check that we get an error when using an invalid thread index id self.expect("thread trace dump instructions 10", error=True, @@ -83,85 +92,87 @@ # We print the instructions of two threads simultaneously 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''']) + substrs=['''thread #1: tid = 3842849 + a.out`main + 32 at main.cpp:4 + [ 0] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 + [-1] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) +thread #2: tid = 3842850 + a.out`main + 32 at main.cpp:4 + [ 0] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 + [-1] 0x0000000000400529 cmpl $0x3, -0x8(%rbp)''']) + + # We use custom --count and --skip, saving the command to history for later + self.expect("thread trace dump instructions 1 2 --count 2 --skip 2", inHistory=True, + substrs=['''thread #1: tid = 3842849 + a.out`main + 24 at main.cpp:4 + [-2] 0x0000000000400525 addl $0x1, -0x8(%rbp) + a.out`main + 20 at main.cpp:5 + [-3] 0x0000000000400521 xorl $0x1, -0x4(%rbp) +thread #2: tid = 3842850 + a.out`main + 24 at main.cpp:4 + [-2] 0x0000000000400525 addl $0x1, -0x8(%rbp) + a.out`main + 20 at main.cpp:5 + [-3] 0x0000000000400521 xorl $0x1, -0x4(%rbp)''']) # 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) + substrs=['''thread #1: tid = 3842849 + a.out`main + 32 at main.cpp:4 + [-4] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 + [-5] 0x0000000000400529 cmpl $0x3, -0x8(%rbp) +thread #2: tid = 3842850 + a.out`main + 32 at main.cpp:4 + [-4] 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 + [-5] 0x0000000000400529 cmpl $0x3, -0x8(%rbp)''']) + + self.expect("", inHistory=True, + substrs=['''thread #1: tid = 3842849 a.out`main + 24 at main.cpp:4 - [18] 0x0000000000400525 addl $0x1, -0x8(%rbp) -thread #2: tid = 3842850, total instructions = 21 + [-6] 0x0000000000400525 addl $0x1, -0x8(%rbp) a.out`main + 20 at main.cpp:5 - [17] 0x0000000000400521 xorl $0x1, -0x4(%rbp) + [-7] 0x0000000000400521 xorl $0x1, -0x4(%rbp) +thread #2: tid = 3842850 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''']) + [-6] 0x0000000000400525 addl $0x1, -0x8(%rbp) + a.out`main + 20 at main.cpp:5 + [-7] 0x0000000000400521 xorl $0x1, -0x4(%rbp)''']) 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 + self.expect("thread trace dump instructions --count 20 --forwards", + substrs=['''thread #1: tid = 3842849 a.out`main + 4 at main.cpp:2 - [0] 0x0000000000400511 movl $0x0, -0x4(%rbp) + [ 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''']) + [ 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') + self.expect("thread trace dump instructions --skip 23", + endstr='no more data\n') # Should fail with negative bounds - self.expect("thread trace dump instructions --position -1", error=True) + self.expect("thread trace dump instructions --skip -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''']) + self.expect("thread trace dump instructions --forwards", + substrs=['''thread #1: tid = 3842849 + [ 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''']) + self.expect("thread trace dump instructions --forwards", + substrs=['''thread #1: tid = 3842849 + [ 0] error: unknown cpu''']) def testMultiFileTraceWithMissingModule(self): self.expect("trace load " + @@ -181,8 +192,8 @@ # 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 + self.expect("thread trace dump instructions --count 50 --forwards", + substrs=['''thread #1: tid = 815455 a.out`main + 15 at main.cpp:10 [ 0] 0x000000000040066f callq 0x400540 ; symbol stub for: foo() a.out`symbol stub for: foo() @@ -252,3 +263,83 @@ [43] 0x00000000004006a4 movl -0xc(%rbp), %ecx [44] 0x00000000004006a7 addl %eax, %ecx [45] 0x00000000004006a9 movl %ecx, -0xc(%rbp)''']) + + + self.expect("thread trace dump instructions --count 50", + substrs=['''thread #1: tid = 815455 + a.out`main + 73 at main.cpp:16 + [ 0] 0x00000000004006a9 movl %ecx, -0xc(%rbp) + [ -1] 0x00000000004006a7 addl %eax, %ecx + [ -2] 0x00000000004006a4 movl -0xc(%rbp), %ecx + libfoo.so`foo() + 35 at foo.cpp:6 + [ -3] 0x00007ffff7bd9703 retq''', + '''[ -4] 0x00007ffff7bd9702 popq %rbp + [ -5] 0x00007ffff7bd96fe addq $0x10, %rsp + [ -6] 0x00007ffff7bd96fb movl -0x4(%rbp), %eax + libfoo.so`foo() + 24 at foo.cpp:5 + [ -7] 0x00007ffff7bd96f8 movl %eax, -0x4(%rbp) + [ -8] 0x00007ffff7bd96f3 addl $0x1, %eax + [ -9] 0x00007ffff7bd96f0 movl -0x4(%rbp), %eax + libfoo.so`foo() + 13 at foo.cpp:4 + [-10] 0x00007ffff7bd96ed movl %eax, -0x4(%rbp) + libbar.so`bar() + 26 at bar.cpp:4 + [-11] 0x00007ffff79d76aa retq''', + '''[-12] 0x00007ffff79d76a9 popq %rbp + [-13] 0x00007ffff79d76a6 movl -0x4(%rbp), %eax + libbar.so`bar() + 19 at bar.cpp:3 + [-14] 0x00007ffff79d76a3 movl %eax, -0x4(%rbp) + [-15] 0x00007ffff79d769e addl $0x1, %eax + [-16] 0x00007ffff79d769b movl -0x4(%rbp), %eax + libbar.so`bar() + 4 at bar.cpp:2 + [-17] 0x00007ffff79d7694 movl $0x1, -0x4(%rbp) + libbar.so`bar() + 1 at bar.cpp:1 + [-18] 0x00007ffff79d7691 movq %rsp, %rbp + [-19] 0x00007ffff79d7690 pushq %rbp + libfoo.so`symbol stub for: bar() + [-20] 0x00007ffff7bd95d0 jmpq *0x200a4a(%rip) ; _GLOBAL_OFFSET_TABLE_ + 32 + libfoo.so`foo() + 8 at foo.cpp:4 + [-21] 0x00007ffff7bd96e8 callq 0x7ffff7bd95d0 ; symbol stub for: bar() + [-22] 0x00007ffff7bd96e4 subq $0x10, %rsp + libfoo.so`foo() + 1 at foo.cpp:3 + [-23] 0x00007ffff7bd96e1 movq %rsp, %rbp + [-24] 0x00007ffff7bd96e0 pushq %rbp + a.out`symbol stub for: foo() + [-25] 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 + a.out`main + 63 at main.cpp:16 + [-26] 0x000000000040069f callq 0x400540 ; symbol stub for: foo() + a.out`main + 60 at main.cpp:14 + [-27] 0x000000000040069c movl %ecx, -0xc(%rbp) + [-28] 0x000000000040069a addl %eax, %ecx + [-29] 0x0000000000400697 movl -0xc(%rbp), %ecx + a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6 + [-30] 0x0000000000400694 movl -0x4(%rbp), %eax + a.out`main + 49 [inlined] inline_function() + 15 at main.cpp:5 + [-31] 0x0000000000400691 movl %eax, -0x4(%rbp) + [-32] 0x000000000040068c addl $0x1, %eax + [-33] 0x0000000000400689 movl -0x4(%rbp), %eax + a.out`main + 34 [inlined] inline_function() at main.cpp:4 + [-34] 0x0000000000400682 movl $0x0, -0x4(%rbp) + a.out`main + 31 at main.cpp:12 + [-35] 0x000000000040067f movl %eax, -0xc(%rbp) + [-36] 0x000000000040067a addl $0x1, %eax + [-37] 0x0000000000400677 movl -0xc(%rbp), %eax + a.out`main + 20 at main.cpp:10 + [-38] 0x0000000000400674 movl %eax, -0xc(%rbp) + ...missing instructions + [-39] 0x00007ffff7df1950 error: no memory mapped at this address + a.out`(none) + [-40] 0x0000000000400516 jmpq *0x200af4(%rip) ; _GLOBAL_OFFSET_TABLE_ + 16 + [-41] 0x0000000000400510 pushq 0x200af2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 8 + a.out`symbol stub for: foo() + 11 + [-42] 0x000000000040054b jmp 0x400510 + [-43] 0x0000000000400546 pushq $0x2 + [-44] 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 + a.out`main + 15 at main.cpp:10 + [-45] 0x000000000040066f callq 0x400540 ; symbol stub for: foo()''']) + + self.expect("thread trace dump instructions --skip 100 --forwards", inHistory=True, + substrs=['''thread #1: tid = 815455 + no more data''']) + + self.expect("", substrs=['''thread #1: tid = 815455 + no more data''']) 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 @@ -65,11 +65,12 @@ self.expect("r") self.expect("thread trace start") self.expect("n") - self.expect("thread trace dump instructions", substrs=["total instructions"]) + self.expect("thread trace dump instructions", substrs=["""0x0000000000400511 movl $0x0, -0x4(%rbp) + no more data"""]) # process stopping should stop the thread self.expect("process trace stop") self.expect("n") - self.expect("thread trace dump instructions", error=True, substrs=["not traced"]) + self.expect("thread trace dump instructions", substrs=["not traced"]) @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @@ -110,22 +111,32 @@ # We can reconstruct the single instruction executed in the first line self.expect("n") - self.expect("thread trace dump instructions", - patterns=[f'''thread #1: tid = .*, total instructions = 1 + self.expect("thread trace dump instructions -f", + patterns=[f'''thread #1: tid = .* a.out`main \+ 4 at main.cpp:2 - \[0\] {ADDRESS_REGEX} movl''']) + \[ 0\] {ADDRESS_REGEX} movl''']) # We can reconstruct the instructions up to the second line self.expect("n") - self.expect("thread trace dump instructions", - patterns=[f'''thread #1: tid = .*, total instructions = 5 + self.expect("thread trace dump instructions -f", + patterns=[f'''thread #1: tid = .* a.out`main \+ 4 at main.cpp:2 - \[0\] {ADDRESS_REGEX} movl .* + \[ 0\] {ADDRESS_REGEX} movl .* a.out`main \+ 11 at main.cpp:4 - \[1\] {ADDRESS_REGEX} movl .* - \[2\] {ADDRESS_REGEX} jmp .* ; <\+28> at main.cpp:4 - \[3\] {ADDRESS_REGEX} cmpl .* - \[4\] {ADDRESS_REGEX} jle .* ; <\+20> at main.cpp:5''']) + \[ 1\] {ADDRESS_REGEX} movl .* + \[ 2\] {ADDRESS_REGEX} jmp .* ; <\+28> at main.cpp:4 + \[ 3\] {ADDRESS_REGEX} cmpl .* + \[ 4\] {ADDRESS_REGEX} jle .* ; <\+20> at main.cpp:5''']) + + self.expect("thread trace dump instructions", + patterns=[f'''thread #1: tid = .* + a.out`main \+ 32 at main.cpp:4 + \[ 0\] {ADDRESS_REGEX} jle .* ; <\+20> at main.cpp:5 + \[ -1\] {ADDRESS_REGEX} cmpl .* + \[ -2\] {ADDRESS_REGEX} jmp .* ; <\+28> at main.cpp:4 + \[ -3\] {ADDRESS_REGEX} movl .* + a.out`main \+ 4 at main.cpp:2 + \[ -4\] {ADDRESS_REGEX} movl .* ''']) # We stop tracing self.expect("thread trace stop") @@ -138,10 +149,15 @@ # thread self.expect("thread trace start") self.expect("n") + self.expect("thread trace dump instructions -f", + patterns=[f'''thread #1: tid = .* + a.out`main \+ 20 at main.cpp:5 + \[ 0\] {ADDRESS_REGEX} xorl''']) + self.expect("thread trace dump instructions", - patterns=[f'''thread #1: tid = .*, total instructions = 1 + patterns=[f'''thread #1: tid = .* a.out`main \+ 20 at main.cpp:5 - \[0\] {ADDRESS_REGEX} xorl''']) + \[ 0\] {ADDRESS_REGEX} xorl''']) self.expect("c") # Now the process has finished, so the commands should fail diff --git a/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py b/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py --- a/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py +++ b/lldb/test/API/commands/trace/multiple-threads/TestTraceStartStopMultipleThreads.py @@ -97,8 +97,8 @@ self.expect("continue") self.expect("thread trace dump instructions", substrs=['main.cpp:4']) self.expect("thread trace dump instructions 3", substrs=['main.cpp:4']) - self.expect("thread trace dump instructions 1", error=True, substrs=['not traced']) - self.expect("thread trace dump instructions 2", error=True, substrs=['not traced']) + self.expect("thread trace dump instructions 1", substrs=['not traced']) + self.expect("thread trace dump instructions 2", substrs=['not traced']) @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) def testStartMultipleLiveThreadsWithThreadStartAll(self): @@ -128,9 +128,9 @@ # We'll stop at the next breakpoint in thread 3, and nothing should be traced self.expect("continue") - self.expect("thread trace dump instructions 3", error=True, substrs=['not traced']) - self.expect("thread trace dump instructions 1", error=True, substrs=['not traced']) - self.expect("thread trace dump instructions 2", error=True, substrs=['not traced']) + self.expect("thread trace dump instructions 3", substrs=['not traced']) + self.expect("thread trace dump instructions 1", substrs=['not traced']) + self.expect("thread trace dump instructions 2", substrs=['not traced']) @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @testSBAPIAndCommands