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 @@ -74,9 +74,10 @@ public: /// Helper enum to indicate the reference point when invoking /// \a TraceCursor::Seek(). + /// The following values are inspired by \a std::istream::seekg. enum class SeekType { /// The beginning of the trace, i.e the oldest item. - Set = 0, + Beginning = 0, /// The current position in the trace. Current, /// The end of the trace, i.e the most recent item. @@ -133,6 +134,51 @@ /// \b true if the cursor effectively moved, \b false otherwise. virtual bool Next() = 0; + /// Instruction identifiers: + /// + /// When building complex higher level tools, fast random accesses in the + /// trace might be needed, for which each instruction requires a unique + /// identifier within its thread trace. For example, a tool might want to + /// repeatedly inspect random consecutive portions of a trace. This means that + /// it will need to first move quickly to the beginning of each section and + /// then start its iteration. Given that the number of instructions can be in + /// the order of hundreds of millions, fast random access is necessary. + /// + /// An example of such a tool could be an inspector of the call graph of a + /// trace, where each call is represented with its start and end instructions. + /// Inspecting all the instructions of a call requires moving to its first + /// instruction and then iterating until the last instruction, which following + /// the pattern explained above. + /// + /// Instead of using 0-based indices as identifiers, each Trace plug-in can + /// decide the nature of these identifiers and thus no assumptions can be made + /// regarding their ordering and sequentiality. The reason is that an + /// instruction might be encoded by the plug-in in a way that hides its actual + /// 0-based index in the trace, but it's still possible to efficiently find + /// it. + /// + /// Requirements: + /// - For a given thread, no two instructions have the same id. + /// - In terms of efficiency, moving the cursor to a given id should be as + /// fast as possible, but not necessarily O(1). That's why the recommended + /// way to traverse sequential instructions is to use the \a + /// TraceCursor::Next() method and only use \a TraceCursor::GoToId(id) + /// sparingly. + + /// Make the cursor point to the item whose identifier is \p id. + /// + /// \return + /// \b true if the given identifier exists and the cursor effectively + /// moved. Otherwise, \b false is returned and the cursor doesn't change + /// its position. + virtual bool GoToId(lldb::user_id_t id) = 0; + + /// \return + /// A unique identifier for the instruction or error this cursor is + /// pointing to. + virtual lldb::user_id_t GetId() const = 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. @@ -152,7 +198,7 @@ /// /// \return /// The number of trace items moved from the origin. - virtual size_t Seek(ssize_t offset, SeekType origin) = 0; + virtual uint64_t Seek(int64_t offset, SeekType origin) = 0; /// \return /// The \a ExecutionContextRef of the backing thread from the creation time diff --git a/lldb/include/lldb/Target/TraceInstructionDumper.h b/lldb/include/lldb/Target/TraceInstructionDumper.h --- a/lldb/include/lldb/Target/TraceInstructionDumper.h +++ b/lldb/include/lldb/Target/TraceInstructionDumper.h @@ -13,6 +13,26 @@ namespace lldb_private { +/// Class that holds the configuration used by \a TraceInstructionDumper for +/// traversing and dumping instructions. +struct TraceInstructionDumperOptions { + /// If \b true, the cursor will be iterated forwards starting from the + /// oldest instruction. Otherwise, the iteration starts from the most + /// recent instruction. + bool forwards = false; + /// Dump only instruction addresses without disassembly nor symbol + /// information. + bool raw = false; + /// For each instruction, print the corresponding timestamp counter if + /// available. + bool show_tsc = false; + /// Optional custom id to start traversing from. + llvm::Optional id = llvm::None; + /// Optional number of instructions to skip from the starting position + /// of the cursor. + llvm::Optional skip = llvm::None; +}; + /// Class used to dump the instructions of a \a TraceCursor using its current /// state and granularity. class TraceInstructionDumper { @@ -22,20 +42,13 @@ /// \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. + /// \param[in] s + /// The stream where to dump the instructions to. /// - /// \param[in] show_tsc - /// For each instruction, print the corresponding timestamp counter if - /// available. - TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, int initial_index = 0, - bool raw = false, bool show_tsc = false); + /// \param[in] options + /// Additional options for configuring the dumping. + TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, Stream &s, + const TraceInstructionDumperOptions &options); /// Dump \a count instructions of the thread trace starting at the current /// cursor position. @@ -43,21 +56,23 @@ /// 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 + /// The instruction id of the last traversed instruction, or \b llvm::None + /// if no instructions were visited. + llvm::Optional DumpInstructions(size_t count); /// \return /// \b true if there's still more data to traverse in the trace. bool HasMoreData(); private: + /// Indicate to the dumper that no more data is available in the trace. + /// This will prevent further iterations. + void SetNoMoreData(); + /// Move the cursor one step. /// /// \return @@ -65,9 +80,8 @@ bool TryMoveOneStep(); lldb::TraceCursorUP m_cursor_up; - int m_index; - bool m_raw; - bool m_show_tsc; + TraceInstructionDumperOptions m_options; + Stream &m_s; /// If \b true, all the instructions have been traversed. bool m_no_more_data = false; }; 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 @@ -2099,8 +2099,7 @@ #define LLDB_OPTIONS_thread_trace_dump_instructions #include "CommandOptions.inc" -class CommandObjectTraceDumpInstructions - : public CommandObjectIterateOverThreads { +class CommandObjectTraceDumpInstructions : public CommandObjectParsed { public: class CommandOptions : public Options { public: @@ -2132,19 +2131,29 @@ "invalid integer value for option '%s'", option_arg.str().c_str()); else - m_skip = skip; + m_dumper_options.skip = skip; + break; + } + case 'i': { + uint64_t id; + if (option_arg.empty() || option_arg.getAsInteger(0, id)) + error.SetErrorStringWithFormat( + "invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_dumper_options.id = id; break; } case 'r': { - m_raw = true; + m_dumper_options.raw = true; break; } case 'f': { - m_forwards = true; + m_dumper_options.forwards = true; break; } case 't': { - m_show_tsc = true; + m_dumper_options.show_tsc = true; break; } case 'C': { @@ -2159,11 +2168,8 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_count = kDefaultCount; - m_skip = 0; - m_raw = false; - m_forwards = false; - m_show_tsc = false; m_continue = false; + m_dumper_options = {}; } llvm::ArrayRef GetDefinitions() override { @@ -2174,23 +2180,19 @@ // Instance variables to hold the values for command options. size_t m_count; - size_t m_skip; - bool m_raw; - bool m_forwards; - bool m_show_tsc; - bool m_continue; + size_t m_continue; + TraceInstructionDumperOptions m_dumper_options; }; CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) - : CommandObjectIterateOverThreads( + : CommandObjectParsed( interpreter, "thread trace dump instructions", - "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.", + "Dump the traced instructions for one thread. If no " + "thread is specified, show the current thread.", nullptr, - eCommandRequiresProcess | eCommandTryTargetAPILock | - eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | - eCommandProcessMustBeTraced) {} + eCommandRequiresProcess | eCommandRequiresThread | + eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused | eCommandProcessMustBeTraced) {} ~CommandObjectTraceDumpInstructions() override = default; @@ -2200,57 +2202,63 @@ uint32_t index) override { std::string cmd; current_command_args.GetCommandString(cmd); - if (cmd.find("--continue") == std::string::npos) + if (cmd.find(" --continue") == std::string::npos) cmd += " --continue"; return cmd; } protected: - bool DoExecute(Args &args, CommandReturnObject &result) override { - if (!m_options.m_continue) - m_dumpers.clear(); + ThreadSP GetThread(Args &args, CommandReturnObject &result) { + if (args.GetArgumentCount() == 0) + return m_exe_ctx.GetThreadSP(); - return CommandObjectIterateOverThreads::DoExecute(args, result); - } - - bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { - Stream &s = result.GetOutputStream(); + const char *arg = args.GetArgumentAtIndex(0); + uint32_t thread_idx; - const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); + if (!llvm::to_integer(arg, thread_idx)) { + result.AppendErrorWithFormat("invalid thread specification: \"%s\"\n", + arg); + return nullptr; + } ThreadSP thread_sp = - m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); - - 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(); + m_exe_ctx.GetProcessRef().GetThreadList().FindThreadByIndexID( + thread_idx); + if (!thread_sp) + result.AppendErrorWithFormat("no thread with index: \"%s\"\n", arg); + return thread_sp; + } - auto dumper = std::make_unique( - std::move(cursor_up), initial_index, m_options.m_raw, - m_options.m_show_tsc); + bool DoExecute(Args &args, CommandReturnObject &result) override { + ThreadSP thread_sp = GetThread(args, result); + if (!thread_sp) + return false; - // 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(); + Stream &s = result.GetOutputStream(); + s.Printf("thread #%u: tid = %" PRIu64 "\n", thread_sp->GetIndexID(), + thread_sp->GetID()); - m_dumpers[thread_sp->GetID()] = std::move(dumper); + if (m_options.m_continue) { + if (!m_last_id) { + result.AppendMessage(" no more data\n"); + return true; + } + // We set up the options to continue one instruction past where + // the previous iteration stopped. + m_options.m_dumper_options.skip = 1; + m_options.m_dumper_options.id = m_last_id; } - m_dumpers[thread_sp->GetID()]->DumpInstructions(s, m_options.m_count); + const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); + TraceInstructionDumper dumper(trace_sp->GetCursor(*thread_sp), s, + m_options.m_dumper_options); + m_last_id = dumper.DumpInstructions(m_options.m_count); return true; } CommandOptions m_options; - std::map> m_dumpers; + // Last traversed id used to continue a repeat command. None means + // that all the trace has been consumed. + llvm::Optional m_last_id; }; // CommandObjectTraceDumpInfo 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 @@ -1097,7 +1097,8 @@ } let Command = "thread trace dump instructions" in { - def thread_trace_dump_instructions_forwards: Option<"forwards", "f">, Group<1>, + 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.">; @@ -1105,17 +1106,21 @@ Arg<"Count">, 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>, + def thread_trace_dump_instructions_id: Option<"id", "i">, Group<1>, Arg<"Index">, - Desc<"How many instruction to skip from the end of the trace to start " - "dumping instructions, or from the beginning if --forwards is provided">; + Desc<"Custom starting instruction id from where to start traversing. This " + "id can be provided in decimal or hexadecimal representation.">; + def thread_trace_dump_instructions_skip: Option<"skip", "s">, Group<1>, + Arg<"Index">, + Desc<"How many instruction to skip from the starting position of the trace " + "before starting the traversal.">; def thread_trace_dump_instructions_raw : Option<"raw", "r">, Group<1>, - Desc<"Dump only instruction address without disassembly nor symbol information.">; - def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, - Group<1>, - Desc<"For each instruction, print the corresponding timestamp counter if available.">; + Desc<"Dump only instruction address without disassembly nor symbol " + "information.">; + def thread_trace_dump_instructions_show_tsc : Option<"tsc", "t">, Group<1>, + Desc<"For each instruction, print the corresponding timestamp counter if " + "available.">; def thread_trace_dump_instructions_continue: Option<"continue", "C">, Group<1>, Desc<"Continue dumping instructions right where the previous invocation of this " 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 @@ -71,13 +71,13 @@ class TscRange { public: /// Check if this TSC range includes the given instruction index. - bool InRange(size_t insn_index); + bool InRange(size_t insn_index) const; /// Get the next range chronologically. - llvm::Optional Next(); + llvm::Optional Next() const; /// Get the previous range chronologically. - llvm::Optional Prev(); + llvm::Optional Prev() const; /// Get the TSC value. size_t GetTsc() const; @@ -150,7 +150,17 @@ /// If the trace was collected with TSC support, all the instructions of /// the trace will have associated TSCs. This means that this method will /// only return \b llvm::None if there are no TSCs whatsoever in the trace. - llvm::Optional CalculateTscRange(size_t insn_index) const; + /// + /// \param[in] insn_index + /// The instruction index in question. + /// + /// \param[in] hint_range + /// An optional range that might include the given index or might be a + /// neighbor of it. It might help speed it traversals of the trace with + /// short jumps. + llvm::Optional CalculateTscRange( + size_t insn_index, + const llvm::Optional &hint_range) const; /// Check if an instruction given by its index is an error. bool IsInstructionAnError(size_t insn_idx) const; 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 @@ -112,7 +112,7 @@ m_errors.try_emplace(m_instruction_ips.size(), toString(std::move(error))); m_instruction_ips.emplace_back(LLDB_INVALID_ADDRESS); m_instruction_sizes.emplace_back(0); - m_instruction_classes.emplace_back(pt_insn_class::ptic_unknown); + m_instruction_classes.emplace_back(pt_insn_class::ptic_error); } void DecodedThread::AppendError(llvm::Error &&error, uint64_t tsc) { @@ -133,8 +133,25 @@ return m_tsc_errors; } -Optional -DecodedThread::CalculateTscRange(size_t insn_index) const { +Optional DecodedThread::CalculateTscRange( + size_t insn_index, + const Optional &hint_range) const { + // We first try to check the given hint range in case we are traversing the + // trace in short jumps. If that fails, then we do the more expensive + // arbitrary lookup. + if (hint_range) { + Optional candidate_range; + if (insn_index < hint_range->GetStartInstructionIndex()) + candidate_range = hint_range->Prev(); + else if (insn_index > hint_range->GetEndInstructionIndex()) + candidate_range = hint_range->Next(); + else + candidate_range = hint_range; + + if (candidate_range && candidate_range->InRange(insn_index)) + return candidate_range; + } + // Now we do a more expensive lookup auto it = m_instruction_timestamps.upper_bound(insn_index); if (it == m_instruction_timestamps.begin()) return None; @@ -199,12 +216,12 @@ return m_end_index; } -bool DecodedThread::TscRange::InRange(size_t insn_index) { +bool DecodedThread::TscRange::InRange(size_t insn_index) const { return GetStartInstructionIndex() <= insn_index && insn_index <= GetEndInstructionIndex(); } -Optional DecodedThread::TscRange::Next() { +Optional DecodedThread::TscRange::Next() const { auto next_it = m_it; ++next_it; if (next_it == m_decoded_thread->m_instruction_timestamps.end()) @@ -212,7 +229,7 @@ return TscRange(next_it, *m_decoded_thread); } -Optional DecodedThread::TscRange::Prev() { +Optional DecodedThread::TscRange::Prev() const { if (m_it == m_decoded_thread->m_instruction_timestamps.begin()) return None; auto prev_it = m_it; diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -20,7 +20,7 @@ TraceCursorIntelPT(lldb::ThreadSP thread_sp, DecodedThreadSP decoded_thread_sp); - size_t Seek(int64_t offset, SeekType origin) override; + uint64_t Seek(int64_t offset, SeekType origin) override; virtual bool Next() override; @@ -35,6 +35,10 @@ bool IsError() override; + bool GoToId(lldb::user_id_t id) override; + + lldb::user_id_t GetId() const override; + private: size_t GetInternalInstructionSize(); diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -23,7 +23,8 @@ assert(m_decoded_thread_sp->GetInstructionsCount() > 0 && "a trace should have at least one instruction or error"); m_pos = m_decoded_thread_sp->GetInstructionsCount() - 1; - m_tsc_range = m_decoded_thread_sp->CalculateTscRange(m_pos); + m_tsc_range = + m_decoded_thread_sp->CalculateTscRange(m_pos, /*hint_range*/ None); } size_t TraceCursorIntelPT::GetInternalInstructionSize() { @@ -56,7 +57,7 @@ return false; } -size_t TraceCursorIntelPT::Seek(int64_t offset, SeekType origin) { +uint64_t TraceCursorIntelPT::Seek(int64_t offset, SeekType origin) { int64_t last_index = GetInternalInstructionSize() - 1; auto fitPosToBounds = [&](int64_t raw_pos) -> int64_t { @@ -65,7 +66,7 @@ auto FindDistanceAndSetPos = [&]() -> int64_t { switch (origin) { - case TraceCursor::SeekType::Set: + case TraceCursor::SeekType::Beginning: m_pos = fitPosToBounds(offset); return m_pos; case TraceCursor::SeekType::End: @@ -80,7 +81,7 @@ }; int64_t dist = FindDistanceAndSetPos(); - m_tsc_range = m_decoded_thread_sp->CalculateTscRange(m_pos); + m_tsc_range = m_decoded_thread_sp->CalculateTscRange(m_pos, m_tsc_range); return dist; } @@ -111,3 +112,14 @@ TraceCursorIntelPT::GetInstructionControlFlowType() { return m_decoded_thread_sp->GetInstructionControlFlowType(m_pos); } + +bool TraceCursorIntelPT::GoToId(user_id_t id) { + if (m_decoded_thread_sp->GetInstructionsCount() <= id) + return false; + m_pos = id; + m_tsc_range = m_decoded_thread_sp->CalculateTscRange(m_pos, m_tsc_range); + + return true; +} + +user_id_t TraceCursorIntelPT::GetId() const { return m_pos; } diff --git a/lldb/source/Plugins/TraceExporter/common/TraceHTR.cpp b/lldb/source/Plugins/TraceExporter/common/TraceHTR.cpp --- a/lldb/source/Plugins/TraceExporter/common/TraceHTR.cpp +++ b/lldb/source/Plugins/TraceExporter/common/TraceHTR.cpp @@ -130,7 +130,7 @@ // Move cursor to the first instruction in the trace cursor.SetForwards(true); - cursor.Seek(0, TraceCursor::SeekType::Set); + cursor.Seek(0, TraceCursor::SeekType::Beginning); Target &target = thread.GetProcess()->GetTarget(); auto function_name_from_load_address = diff --git a/lldb/source/Target/TraceInstructionDumper.cpp b/lldb/source/Target/TraceInstructionDumper.cpp --- a/lldb/source/Target/TraceInstructionDumper.cpp +++ b/lldb/source/Target/TraceInstructionDumper.cpp @@ -18,11 +18,34 @@ using namespace lldb_private; using namespace llvm; -TraceInstructionDumper::TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up, - int initial_index, bool raw, - bool show_tsc) - : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw), - m_show_tsc(show_tsc) {} +TraceInstructionDumper::TraceInstructionDumper( + lldb::TraceCursorUP &&cursor_up, Stream &s, + const TraceInstructionDumperOptions &options) + : m_cursor_up(std::move(cursor_up)), m_options(options), m_s(s) { + // We first set the cursor in its initial position + if (m_options.id) { + if (!m_cursor_up->GoToId(*m_options.id)) { + s.PutCString(" invalid instruction id\n"); + SetNoMoreData(); + return; + } + } else if (m_options.forwards) { + m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning); + } else { + m_cursor_up->Seek(0, TraceCursor::SeekType::End); + } + + m_cursor_up->SetForwards(m_options.forwards); + if (m_options.skip) { + uint64_t to_skip = m_options.skip.getValue(); + if (m_cursor_up->Seek((m_options.forwards ? 1 : -1) * to_skip, + TraceCursor::SeekType::Current) < to_skip) { + // This happens when the skip value was more than the number of + // available instructions. + SetNoMoreData(); + } + } +} /// \return /// Return \b true if the cursor could move one step. @@ -31,19 +54,9 @@ 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 { @@ -159,36 +172,31 @@ bool TraceInstructionDumper::HasMoreData() { return !m_no_more_data; } -void TraceInstructionDumper::DumpInstructions(Stream &s, size_t count) { +Optional TraceInstructionDumper::DumpInstructions(size_t count) { ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP(); if (!thread_sp) { - s.Printf("invalid thread"); - return; + m_s.Printf("invalid thread"); + return None; } - 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"); + m_s.Printf(" ...missing instructions\n"); }; - auto printInstructionIndex = [&]() { - s.Printf(" [%*d] ", digits_count, m_index); + auto printInstructionHeader = [&](uint64_t id) { + m_s.Printf(" %" PRIu64 ": ", id); - if (m_show_tsc) { - s.Printf("[tsc="); + if (m_options.show_tsc) { + m_s.Printf("[tsc="); if (Optional timestamp = m_cursor_up->GetCounter(lldb::eTraceCounterTSC)) - s.Printf("0x%016" PRIx64, *timestamp); + m_s.Printf("0x%016" PRIx64, *timestamp); else - s.Printf("unavailable"); + m_s.Printf("unavailable"); - s.Printf("] "); + m_s.Printf("] "); } }; @@ -244,11 +252,13 @@ : InstructionSP()); }; + Optional last_id; for (size_t i = 0; i < count; i++) { if (!HasMoreData()) { - s.Printf(" no more data\n"); + m_s.Printf(" no more data\n"); break; } + last_id = m_cursor_up->GetId(); if (const char *err = m_cursor_up->GetError()) { if (!m_cursor_up->IsForwards() && !was_prev_instruction_an_error) @@ -256,8 +266,8 @@ was_prev_instruction_an_error = true; - printInstructionIndex(); - s << err; + printInstructionHeader(m_cursor_up->GetId()); + m_s << err; } else { if (m_cursor_up->IsForwards() && was_prev_instruction_an_error) printMissingInstructionsMessage(); @@ -266,7 +276,7 @@ InstructionSymbolInfo insn_info; - if (!m_raw) { + if (!m_options.raw) { insn_info.load_address = m_cursor_up->GetLoadAddress(); insn_info.exe_ctx = exe_ctx; insn_info.address.SetLoadAddress(insn_info.load_address, &target); @@ -274,19 +284,20 @@ std::tie(insn_info.disassembler, insn_info.instruction) = calculateDisass(insn_info.address, insn_info.sc); - DumpInstructionSymbolContext(s, prev_insn_info, insn_info); + DumpInstructionSymbolContext(m_s, prev_insn_info, insn_info); } - printInstructionIndex(); - s.Printf("0x%016" PRIx64, m_cursor_up->GetLoadAddress()); + printInstructionHeader(m_cursor_up->GetId()); + m_s.Printf("0x%016" PRIx64, m_cursor_up->GetLoadAddress()); - if (!m_raw) - DumpInstructionDisassembly(s, insn_info); + if (!m_options.raw) + DumpInstructionDisassembly(m_s, insn_info); prev_insn_info = insn_info; } - s.Printf("\n"); + m_s.Printf("\n"); TryMoveOneStep(); } + return last_id; } 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 @@ -37,49 +37,67 @@ self.expect("thread trace dump instructions --raw --count 21 --forwards", substrs=['''thread #1: tid = 3842849 - [ 0] 0x0000000000400511 - [ 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''']) + 0: 0x0000000000400511 + 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 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''']) + 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''']) + 14: 0x0000000000400525 + 13: 0x0000000000400521 + 12: 0x000000000040052d + 11: 0x0000000000400529 + 10: 0x0000000000400525''']) + + # We check if we can pass count and skip and instruction id in hex + self.expect("thread trace dump instructions --count 5 --skip 6 --raw --id 0xA", + substrs=['''thread #1: tid = 3842849 + 4: 0x000000000040052d + 3: 0x0000000000400529 + 2: 0x000000000040051f + 1: 0x0000000000400518 + 0: 0x0000000000400511''']) + + # We check if we can pass count and skip and instruction id in decimal + self.expect("thread trace dump instructions --count 5 --skip 6 --raw --id 10", + substrs=['''thread #1: tid = 3842849 + 4: 0x000000000040052d + 3: 0x0000000000400529 + 2: 0x000000000040051f + 1: 0x0000000000400518 + 0: 0x0000000000400511''']) # We check if we can access the thread by index id self.expect("thread trace dump instructions 1 --raw", substrs=['''thread #1: tid = 3842849 - [ 0] 0x000000000040052d''']) + 20: 0x000000000040052d''']) # We check that we get an error when using an invalid thread index id self.expect("thread trace dump instructions 10", error=True, @@ -90,53 +108,35 @@ 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 --count 2", - 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 + # We print the instructions of a specific thread + self.expect("thread trace dump instructions 2 --count 2", + substrs=['''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)''']) + 20: 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 + 19: 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 + self.expect("thread trace dump instructions 2 --count 2 --skip 2", inHistory=True, + substrs=['''thread #2: tid = 3842850 a.out`main + 24 at main.cpp:4 - [-2] 0x0000000000400525 addl $0x1, -0x8(%rbp) + 18: 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)''']) + 17: 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 - 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 + substrs=['''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)''']) + 16: 0x000000000040052d jle 0x400521 ; <+20> at main.cpp:5 + 15: 0x0000000000400529 cmpl $0x3, -0x8(%rbp)''']) self.expect("", inHistory=True, - substrs=['''thread #1: tid = 3842849 - a.out`main + 24 at main.cpp:4 - [-6] 0x0000000000400525 addl $0x1, -0x8(%rbp) - a.out`main + 20 at main.cpp:5 - [-7] 0x0000000000400521 xorl $0x1, -0x4(%rbp) -thread #2: tid = 3842850 + substrs=['''thread #2: tid = 3842850 a.out`main + 24 at main.cpp:4 - [-6] 0x0000000000400525 addl $0x1, -0x8(%rbp) + 14: 0x0000000000400525 addl $0x1, -0x8(%rbp) a.out`main + 20 at main.cpp:5 - [-7] 0x0000000000400521 xorl $0x1, -0x4(%rbp)''']) + 13: 0x0000000000400521 xorl $0x1, -0x4(%rbp)''']) def testInvalidBounds(self): self.expect("trace load -v " + @@ -146,10 +146,10 @@ 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 --skip 23", @@ -164,15 +164,15 @@ os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json")) 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''']) + 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 --forwards", substrs=['''thread #1: tid = 3842849 - [ 0] error: unknown cpu''']) + 0: error: unknown cpu''']) def testMultiFileTraceWithMissingModule(self): self.expect("trace load " + @@ -195,147 +195,147 @@ 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() + 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 + 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 + 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) + 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) + 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) + 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) + 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 + 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) + 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() + 19: 0x000000000040069f callq 0x400540 ; symbol stub for: foo() a.out`symbol stub for: foo() - [20] 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 + 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 + 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() + 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 + 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 + 26: 0x00007ffff79d7690 pushq %rbp + 27: 0x00007ffff79d7691 movq %rsp, %rbp libbar.so`bar() + 4 at bar.cpp:2 - [28] 0x00007ffff79d7694 movl $0x1, -0x4(%rbp) + 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) + 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''', + 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) + 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) + 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''', + 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)''']) + 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 + 45: 0x00000000004006a9 movl %ecx, -0xc(%rbp) + 44: 0x00000000004006a7 addl %eax, %ecx + 43: 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 + 42: 0x00007ffff7bd9703 retq''', + '''41: 0x00007ffff7bd9702 popq %rbp + 40: 0x00007ffff7bd96fe addq $0x10, %rsp + 39: 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 + 38: 0x00007ffff7bd96f8 movl %eax, -0x4(%rbp) + 37: 0x00007ffff7bd96f3 addl $0x1, %eax + 36: 0x00007ffff7bd96f0 movl -0x4(%rbp), %eax libfoo.so`foo() + 13 at foo.cpp:4 - [-10] 0x00007ffff7bd96ed movl %eax, -0x4(%rbp) + 35: 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 + 34: 0x00007ffff79d76aa retq''', + '''33: 0x00007ffff79d76a9 popq %rbp + 32: 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 + 31: 0x00007ffff79d76a3 movl %eax, -0x4(%rbp) + 30: 0x00007ffff79d769e addl $0x1, %eax + 29: 0x00007ffff79d769b movl -0x4(%rbp), %eax libbar.so`bar() + 4 at bar.cpp:2 - [-17] 0x00007ffff79d7694 movl $0x1, -0x4(%rbp) + 28: 0x00007ffff79d7694 movl $0x1, -0x4(%rbp) libbar.so`bar() + 1 at bar.cpp:1 - [-18] 0x00007ffff79d7691 movq %rsp, %rbp - [-19] 0x00007ffff79d7690 pushq %rbp + 27: 0x00007ffff79d7691 movq %rsp, %rbp + 26: 0x00007ffff79d7690 pushq %rbp libfoo.so`symbol stub for: bar() - [-20] 0x00007ffff7bd95d0 jmpq *0x200a4a(%rip) ; _GLOBAL_OFFSET_TABLE_ + 32 + 25: 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 + 24: 0x00007ffff7bd96e8 callq 0x7ffff7bd95d0 ; symbol stub for: bar() + 23: 0x00007ffff7bd96e4 subq $0x10, %rsp libfoo.so`foo() + 1 at foo.cpp:3 - [-23] 0x00007ffff7bd96e1 movq %rsp, %rbp - [-24] 0x00007ffff7bd96e0 pushq %rbp + 22: 0x00007ffff7bd96e1 movq %rsp, %rbp + 21: 0x00007ffff7bd96e0 pushq %rbp a.out`symbol stub for: foo() - [-25] 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 + 20: 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 a.out`main + 63 at main.cpp:16 - [-26] 0x000000000040069f callq 0x400540 ; symbol stub for: foo() + 19: 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 + 18: 0x000000000040069c movl %ecx, -0xc(%rbp) + 17: 0x000000000040069a addl %eax, %ecx + 16: 0x0000000000400697 movl -0xc(%rbp), %ecx a.out`main + 52 [inlined] inline_function() + 18 at main.cpp:6 - [-30] 0x0000000000400694 movl -0x4(%rbp), %eax + 15: 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 + 14: 0x0000000000400691 movl %eax, -0x4(%rbp) + 13: 0x000000000040068c addl $0x1, %eax + 12: 0x0000000000400689 movl -0x4(%rbp), %eax a.out`main + 34 [inlined] inline_function() at main.cpp:4 - [-34] 0x0000000000400682 movl $0x0, -0x4(%rbp) + 11: 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 + 10: 0x000000000040067f movl %eax, -0xc(%rbp) + 9: 0x000000000040067a addl $0x1, %eax + 8: 0x0000000000400677 movl -0xc(%rbp), %eax a.out`main + 20 at main.cpp:10 - [-38] 0x0000000000400674 movl %eax, -0xc(%rbp) + 7: 0x0000000000400674 movl %eax, -0xc(%rbp) ...missing instructions - [-39] 0x00007ffff7df1950 error: no memory mapped at this address + 6: 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 + 5: 0x0000000000400516 jmpq *0x200af4(%rip) ; _GLOBAL_OFFSET_TABLE_ + 16 + 4: 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 + 3: 0x000000000040054b jmp 0x400510 + 2: 0x0000000000400546 pushq $0x2 + 1: 0x0000000000400540 jmpq *0x200ae2(%rip) ; _GLOBAL_OFFSET_TABLE_ + 40 a.out`main + 15 at main.cpp:10 - [-45] 0x000000000040066f callq 0x400540 ; symbol stub for: foo()''']) + 0: 0x000000000040066f callq 0x400540 ; symbol stub for: foo()''']) self.expect("thread trace dump instructions --skip 100 --forwards", inHistory=True, substrs=['''thread #1: tid = 815455 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 @@ -114,29 +114,29 @@ 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 -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 .* + 4: {ADDRESS_REGEX} jle .* ; <\+20> at main.cpp:5 + 3: {ADDRESS_REGEX} cmpl .* + 2: {ADDRESS_REGEX} jmp .* ; <\+28> at main.cpp:4 + 1: {ADDRESS_REGEX} movl .* a.out`main \+ 4 at main.cpp:2 - \[ -4\] {ADDRESS_REGEX} movl .* ''']) + 0: {ADDRESS_REGEX} movl .* ''']) # We stop tracing self.expect("thread trace stop") @@ -152,12 +152,12 @@ self.expect("thread trace dump instructions -f", patterns=[f'''thread #1: tid = .* a.out`main \+ 20 at main.cpp:5 - \[ 0\] {ADDRESS_REGEX} xorl''']) + 0: {ADDRESS_REGEX} xorl''']) self.expect("thread trace dump instructions", 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/TestTraceTimestampCounters.py b/lldb/test/API/commands/trace/TestTraceTSC.py rename from lldb/test/API/commands/trace/TestTraceTimestampCounters.py rename to lldb/test/API/commands/trace/TestTraceTSC.py --- a/lldb/test/API/commands/trace/TestTraceTimestampCounters.py +++ b/lldb/test/API/commands/trace/TestTraceTSC.py @@ -19,7 +19,35 @@ self.expect("n") self.expect("thread trace dump instructions --tsc -c 1", - patterns=["\[0\] \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + patterns=["0: \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + + @testSBAPIAndCommands + @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) + def testMultipleTscsPerThread(self): + self.expect("file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + + self.traceStartThread(enableTsc=True) + + # After each stop there'll be a new TSC + self.expect("n") + self.expect("n") + self.expect("n") + + # We'll get the most recent instructions, with at least 3 different TSCs + self.runCmd("thread trace dump instructions --tsc --raw") + id_to_tsc = {} + for line in self.res.GetOutput().splitlines(): + m = re.search(" (.+): \[tsc=(.+)\].*", line) + if m: + id_to_tsc[int(m.group(1))] = m.group(2) + self.assertEqual(len(id_to_tsc), 6) + + # We check that the values are right when dumping a specific id + for id in range(0, 6): + self.expect(f"thread trace dump instructions --tsc --id {id} -c 1", + substrs=[f"{id}: [tsc={id_to_tsc[id]}]"]) @testSBAPIAndCommands @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @@ -32,7 +60,7 @@ self.expect("n") self.expect("thread trace dump instructions --tsc -c 1", - patterns=["\[0\] \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) + patterns=["0: \[tsc=0x[0-9a-fA-F]+\] 0x0000000000400511 movl"]) @testSBAPIAndCommands @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64'])) @@ -45,7 +73,7 @@ self.expect("n") self.expect("thread trace dump instructions --tsc -c 1", - patterns=["\[0\] \[tsc=unavailable\] 0x0000000000400511 movl"]) + patterns=["0: \[tsc=unavailable\] 0x0000000000400511 movl"]) @testSBAPIAndCommands @skipIf(oslist=no_match(['linux']), archs=no_match(['i386', 'x86_64']))