diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -1105,6 +1105,20 @@ void ClearAllLoadedSections(); + /// Set the \a Trace object containing processor trace information of this + /// target. + /// + /// \param[in] trace_sp + /// The trace object. + void SetTrace(const lldb::TraceSP &trace_sp); + + /// Get the \a Trace object containing processor trace information of this + /// target. + /// + /// \return + /// The trace object. It might be undefined. + lldb::TraceSP &GetTrace(); + // Since expressions results can persist beyond the lifetime of a process, // and the const expression results are available after a process is gone, we // provide a way for expressions to be evaluated from the Target itself. If @@ -1394,6 +1408,9 @@ bool m_suppress_stop_hooks; bool m_is_dummy_target; unsigned m_next_persistent_variable_index = 0; + /// An optional \a lldb_private::Trace object containing processor trace + /// information of this target. + lldb::TraceSP m_trace_sp; /// Stores the frame recognizers of this target. lldb::StackFrameRecognizerManagerUP m_frame_recognizer_manager_up; diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -469,6 +469,24 @@ // the backing thread for all memory threads each time we stop. } + /// Dump \a count instructions of the thread's \a Trace starting at the \a + /// start_position position in reverse order. + /// + /// The instructions are indexed in reverse order, which means that the \a + /// start_position 0 represents the last instruction of the trace + /// chronologically. + /// + /// \param[in] s + /// The stream object where the instructions are printed. + /// + /// \param[in] count + /// The number of instructions to print. + /// + /// \param[in] start_position + /// The position of the first instruction to print. + void DumpTraceInstructions(Stream &s, size_t count, + size_t start_position = 0) const; + // If stop_format is true, this will be the form used when we print stop // info. If false, it will be the form we use for thread list and co. void DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx, 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 @@ -32,7 +32,8 @@ /// Processor trace information can also be fetched through the process /// interfaces during a live debug session if your process supports gathering /// this information. -class Trace : public PluginInterface { +class Trace : public PluginInterface, + public std::enable_shared_from_this { public: /// Dump the trace data that this plug-in has access to. /// 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,8 @@ #include "CommandObjectThread.h" +#include + #include "lldb/Core/ValueObject.h" #include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandInterpreter.h" @@ -2165,6 +2167,164 @@ ~CommandObjectMultiwordThreadPlan() override = default; }; +// Next are the subcommands of CommandObjectMultiwordTrace + +// CommandObjectTraceDumpInstructions +#define LLDB_OPTIONS_thread_trace_dump_instructions +#include "CommandOptions.inc" + +class CommandObjectTraceDumpInstructions + : public CommandObjectIterateOverThreads { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'c': { + int32_t count; + if (option_arg.empty() || option_arg.getAsInteger(0, count) || + count < 0) + error.SetErrorStringWithFormat( + "invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_count = count; + break; + } + case 's': { + int32_t start_position; + if (option_arg.empty() || option_arg.getAsInteger(0, start_position) || + start_position < 0) + error.SetErrorStringWithFormat( + "invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_start_position = start_position; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_count = kDefaultCount; + m_start_position = kDefaultStartPosition; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_thread_trace_dump_instructions_options); + } + + static const uint32_t kDefaultCount = 20; + static const uint32_t kDefaultStartPosition = 0; + + // Instance variables to hold the values for command options. + uint32_t m_count; + uint32_t m_start_position; + }; + + CommandObjectTraceDumpInstructions(CommandInterpreter &interpreter) + : CommandObjectIterateOverThreads( + 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.", + nullptr, + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused), + m_options(), m_create_repeat_command_just_invoked(false) {} + + ~CommandObjectTraceDumpInstructions() override = default; + + Options *GetOptions() override { return &m_options; } + + const char *GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + current_command_args.GetCommandString(m_repeat_command); + m_create_repeat_command_just_invoked = true; + return m_repeat_command.c_str(); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + bool status = CommandObjectIterateOverThreads::DoExecute(args, result); + PrepareRepeatArguments(); + return status; + } + + void PrepareRepeatArguments() { + m_repeat_start_position = m_options.m_count + GetStartPosition(); + m_create_repeat_command_just_invoked = false; + } + + bool IsRepeatCommand() { + return !m_repeat_command.empty() && !m_create_repeat_command_just_invoked; + } + + uint32_t GetStartPosition() { + return IsRepeatCommand() ? m_repeat_start_position + : m_options.m_start_position; + } + + bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + ThreadSP thread_sp = + m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); + + thread_sp->DumpTraceInstructions(result.GetOutputStream(), + m_options.m_count, GetStartPosition()); + return true; + } + + CommandOptions m_options; + + // Repeat command helpers + std::string m_repeat_command; + bool m_create_repeat_command_just_invoked; + uint32_t m_repeat_start_position; +}; + +// CommandObjectMultiwordTraceDump +class CommandObjectMultiwordTraceDump : public CommandObjectMultiword { +public: + CommandObjectMultiwordTraceDump(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "dump", + "Commands for displaying trace information of the threads " + "in the current process.", + "thread trace dump []") { + LoadSubCommand( + "instructions", + CommandObjectSP(new CommandObjectTraceDumpInstructions(interpreter))); + } + ~CommandObjectMultiwordTraceDump() override = default; +}; + +// CommandObjectMultiwordTrace +class CommandObjectMultiwordTrace : public CommandObjectMultiword { +public: + CommandObjectMultiwordTrace(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "trace", + "Commands for operating on traces of the threads in the current " + "process.", + "thread trace []") { + LoadSubCommand("dump", CommandObjectSP(new CommandObjectMultiwordTraceDump( + interpreter))); + } + + ~CommandObjectMultiwordTrace() override = default; +}; + // CommandObjectMultiwordThread CommandObjectMultiwordThread::CommandObjectMultiwordThread( @@ -2240,6 +2400,8 @@ LoadSubCommand("plan", CommandObjectSP(new CommandObjectMultiwordThreadPlan( interpreter))); + LoadSubCommand("trace", + CommandObjectSP(new CommandObjectMultiwordTrace(interpreter))); } CommandObjectMultiwordThread::~CommandObjectMultiwordThread() = default; 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 @@ -1005,6 +1005,21 @@ Desc<"Display thread plans for unreported threads">; } +let Command = "thread trace dump instructions" in { + def thread_trace_dump_instructions_count : Option<"count", "c">, Group<1>, + Arg<"Count">, + Desc<"The number of instructions to display starting at the current " + "position in reverse order chronologically.">; + def thread_trace_dump_instructions_start_position: + Option<"start-position", "s">, + Group<1>, + Arg<"Index">, + Desc<"The position of the first instruction to print. Defaults to the " + "current position, i.e. where the thread is stopped. The instructions are " + "indexed in reverse order, which means that a start position of 0 refers " + "to the last instruction chronologically.">; +} + let Command = "type summary add" in { def type_summary_add_category : Option<"category", "w">, Arg<"Name">, Desc<"Add this to the given category instead of the default one.">; diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt --- a/lldb/source/Plugins/Process/CMakeLists.txt +++ b/lldb/source/Plugins/Process/CMakeLists.txt @@ -17,3 +17,4 @@ add_subdirectory(elf-core) add_subdirectory(mach-core) add_subdirectory(minidump) +add_subdirectory(Trace) diff --git a/lldb/source/Plugins/Process/Trace/CMakeLists.txt b/lldb/source/Plugins/Process/Trace/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Trace/CMakeLists.txt @@ -0,0 +1,13 @@ +add_lldb_library(lldbPluginProcessTrace PLUGIN + ProcessTrace.cpp + + LINK_LIBS + lldbCore + lldbTarget + lldbUtility + lldbPluginProcessUtility + LINK_COMPONENTS + BinaryFormat + Object + Support + ) diff --git a/lldb/source/Plugins/Process/Trace/ProcessTrace.h b/lldb/source/Plugins/Process/Trace/ProcessTrace.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Trace/ProcessTrace.h @@ -0,0 +1,86 @@ +//===-- ProcessTrace.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_PROCESS_TRACE_PROCESSTRACE_H +#define LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H + +#include "lldb/Target/Process.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Status.h" + +namespace lldb_private { +namespace process_trace { + +class ProcessTrace : public Process { +public: + static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *crash_file_path); + + static void Initialize(); + + static void Terminate(); + + static ConstString GetPluginNameStatic(); + + static const char *GetPluginDescriptionStatic(); + + ProcessTrace(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp); + + ~ProcessTrace() override; + + bool CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) override; + + void DidAttach(ArchSpec &process_arch) override; + + DynamicLoader *GetDynamicLoader() override { return nullptr; } + + SystemRuntime *GetSystemRuntime() override { return nullptr; } + + ConstString GetPluginName() override; + + uint32_t GetPluginVersion() override; + + Status DoDestroy() override; + + void RefreshStateAfterStop() override; + + Status WillResume() override { + Status error; + error.SetErrorStringWithFormat( + "error: %s does not support resuming processes", + GetPluginName().GetCString()); + return error; + } + + bool IsAlive() override; + + bool WarnBeforeDetach() const override { return false; } + + size_t ReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + Status &error) override; + + ArchSpec GetArchitecture(); + + bool GetProcessInfo(ProcessInstanceInfo &info) override; + +protected: + void Clear(); + + bool UpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) override; +}; + +} // namespace process_trace +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H diff --git a/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp b/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp @@ -0,0 +1,128 @@ +//===-- ProcessTrace.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 "ProcessTrace.h" + +#include + +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Target.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_trace; + +LLDB_PLUGIN_DEFINE(ProcessTrace) + +ConstString ProcessTrace::GetPluginNameStatic() { + static ConstString g_name("trace"); + return g_name; +} + +const char *ProcessTrace::GetPluginDescriptionStatic() { + return "Trace process plug-in."; +} + +void ProcessTrace::Terminate() { + PluginManager::UnregisterPlugin(ProcessTrace::CreateInstance); +} + +ProcessSP ProcessTrace::CreateInstance(TargetSP target_sp, + ListenerSP listener_sp, + const FileSpec *crash_file) { + return std::make_shared(target_sp, listener_sp); +} + +bool ProcessTrace::CanDebug(TargetSP target_sp, bool plugin_specified_by_name) { + return plugin_specified_by_name; +} + +ProcessTrace::ProcessTrace(TargetSP target_sp, ListenerSP listener_sp) + : Process(target_sp, listener_sp) {} + +ProcessTrace::~ProcessTrace() { + Clear(); + // We need to call finalize on the process before destroying ourselves to + // make sure all of the broadcaster cleanup goes as planned. If we destruct + // this class, then Process::~Process() might have problems trying to fully + // destroy the broadcaster. + Finalize(); +} + +ConstString ProcessTrace::GetPluginName() { return GetPluginNameStatic(); } + +uint32_t ProcessTrace::GetPluginVersion() { return 1; } + +void ProcessTrace::DidAttach(ArchSpec &process_arch) { + ListenerSP listener_sp( + Listener::MakeListener("lldb.process_trace.did_attach_listener")); + HijackProcessEvents(listener_sp); + + SetCanJIT(false); + StartPrivateStateThread(); + SetPrivateState(eStateStopped); + + EventSP event_sp; + WaitForProcessToStop(llvm::None, &event_sp, true, listener_sp); + + RestoreProcessEvents(); + + Process::DidAttach(process_arch); +} + +bool ProcessTrace::UpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + return false; +} + +void ProcessTrace::RefreshStateAfterStop() {} + +Status ProcessTrace::DoDestroy() { return Status(); } + +bool ProcessTrace::IsAlive() { return true; } + +size_t ProcessTrace::ReadMemory(addr_t addr, void *buf, size_t size, + Status &error) { + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // we have it all cached in the trace files. + return DoReadMemory(addr, buf, size, error); +} + +void ProcessTrace::Clear() { m_thread_list.Clear(); } + +void ProcessTrace::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); + }); +} + +ArchSpec ProcessTrace::GetArchitecture() { + return GetTarget().GetArchitecture(); +} + +bool ProcessTrace::GetProcessInfo(ProcessInstanceInfo &info) { + info.Clear(); + info.SetProcessID(GetID()); + info.SetArchitecture(GetArchitecture()); + ModuleSP module_sp = GetTarget().GetExecutableModule(); + if (module_sp) { + const bool add_exe_file_as_first_arg = false; + info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(), + add_exe_file_as_first_arg); + } + return true; +} + +size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size, + Status &error) { + return 0; +} 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 @@ -52,6 +52,21 @@ CreateInstance(const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir, Debugger &debugger); + /// Create an instance of this class. + /// + /// \param[in] pt_cpu + /// The libipt.h cpu information needed for decoding correctling the + /// traces. + /// + /// \param[in] targets + /// The list of targets to associate with this trace instance + /// + /// \return + /// An intel-pt trace instance. + static lldb::TraceSP + CreateInstance(const pt_cpu &pt_cpu, + const std::vector &targets); + static ConstString GetPluginNameStatic(); uint32_t GetPluginVersion() override; @@ -59,13 +74,13 @@ llvm::StringRef GetSchema() override; +private: TraceIntelPT(const pt_cpu &pt_cpu, const std::vector &targets) : Trace(), m_pt_cpu(pt_cpu) { for (const lldb::TargetSP &target_sp : targets) m_targets.push_back(target_sp); } -private: pt_cpu m_pt_cpu; std::vector> m_targets; }; 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 @@ -10,6 +10,7 @@ #include "TraceIntelPTSessionFileParser.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Target/Target.h" using namespace lldb; using namespace lldb_private; @@ -45,12 +46,21 @@ uint32_t TraceIntelPT::GetPluginVersion() { return 1; } -void TraceIntelPT::Dump(lldb_private::Stream *s) const {} +void TraceIntelPT::Dump(Stream *s) const {} -Expected +Expected TraceIntelPT::CreateInstance(const json::Value &trace_session_file, StringRef session_file_dir, Debugger &debugger) { return TraceIntelPTSessionFileParser(debugger, trace_session_file, session_file_dir) .Parse(); } + +TraceSP TraceIntelPT::CreateInstance(const pt_cpu &pt_cpu, + const std::vector &targets) { + TraceSP trace_instance(new TraceIntelPT(pt_cpu, targets)); + for (const TargetSP &target_sp : targets) + target_sp->SetTrace(trace_instance->shared_from_this()); + + return trace_instance; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -60,7 +60,7 @@ m_debugger.GetTargetList().SetSelectedTarget(target_sp.get()); ProcessSP process_sp(target_sp->CreateProcess( - /*listener*/ nullptr, /*plugin_name*/ StringRef(), + /*listener*/ nullptr, "trace", /*crash_file*/ nullptr)); process_sp->SetID(static_cast(process.pid)); @@ -71,7 +71,16 @@ if (Error err = ParseModule(target_sp, module)) return err; } - return Error::success(); + + if (!process.threads.empty()) + process_sp->GetThreadList().SetSelectedThreadByIndexID(0); + + // We invoke DidAttach to create a correct stopped state for the process and + // its threads. + ArchSpec process_arch; + process_sp->DidAttach(process_arch); + + return llvm::Error::success(); } void TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { @@ -105,7 +114,7 @@ return std::move(err); } - return std::make_shared(m_pt_cpu, m_targets); + return TraceIntelPT::CreateInstance(m_pt_cpu, m_targets); } namespace llvm { diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -2965,6 +2965,10 @@ return error; } +void Target::SetTrace(const TraceSP &trace_sp) { m_trace_sp = trace_sp; } + +TraceSP &Target::GetTrace() { return m_trace_sp; } + Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { auto state = eStateInvalid; auto process_sp = GetProcessSP(); diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1614,6 +1614,18 @@ return Status(); } +void Thread::DumpTraceInstructions(Stream &s, size_t count, + size_t start_position) const { + if (!GetProcess()->GetTarget().GetTrace()) { + s << "error: no trace in this target\n"; + return; + } + s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = 1000\n", + GetIndexID(), GetID()); + s.Printf(" would print %zu instructions from position %zu\n", count, + start_position); +} + void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx, bool stop_format) { ExecutionContext exe_ctx(shared_from_this()); diff --git a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -0,0 +1,91 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceDumpInstructions(TestBase): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + if 'intel-pt' not in configuration.enabled_plugins: + self.skipTest("The intel-pt test plugin is not enabled") + + def testErrorMessages(self): + # We first check the output when there are no targets + self.expect("thread trace dump instructions", + substrs=["error: invalid target, create a target using the 'target create' command"], + error=True) + + # We now check the output when there's a non-running target + self.expect("target create " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + + self.expect("thread trace dump instructions", + substrs=["error: invalid process"], + error=True) + + # Now we check the output when there's a running target without a trace + self.expect("b main") + self.expect("run") + + self.expect("thread trace dump instructions", + substrs=["error: no trace in this target"]) + + def testDumpInstructions(self): + self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + substrs=["intel-pt"]) + + self.expect("thread trace dump instructions", + substrs=['thread #1: tid = 3842849, total instructions = 1000', + 'would print 20 instructions from position 0']) + + # We check if we can pass count and offset + self.expect("thread trace dump instructions --count 5 --start-position 10", + substrs=['thread #1: tid = 3842849, total instructions = 1000', + 'would print 5 instructions from position 10']) + + # We check if we can access the thread by index id + self.expect("thread trace dump instructions 1", + substrs=['thread #1: tid = 3842849, total instructions = 1000', + 'would print 20 instructions from position 0']) + + # We check that we get an error when using an invalid thread index id + self.expect("thread trace dump instructions 10", error=True, + substrs=['error: no thread with index: "10"']) + + def testDumpInstructionsWithMultipleThreads(self): + # We load a trace with two threads + self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json")) + + # We print the instructions of two threads simultaneously + self.expect("thread trace dump instructions 1 2", + substrs=['''thread #1: tid = 3842849, total instructions = 1000 + would print 20 instructions from position 0 +thread #2: tid = 3842850, total instructions = 1000 + would print 20 instructions from position 0''']) + + # We use custom --count and --start-position, saving the command to history for later + ci = self.dbg.GetCommandInterpreter() + + result = lldb.SBCommandReturnObject() + ci.HandleCommand("thread trace dump instructions 1 2 --count 12 --start-position 5", result, True) + self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 + would print 12 instructions from position 5 +thread #2: tid = 3842850, total instructions = 1000 + would print 12 instructions from position 5''', result.GetOutput()) + + # We use a repeat command and ensure the previous count is used and the start-position has moved to the next position + result = lldb.SBCommandReturnObject() + ci.HandleCommand("", result) + self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 + would print 12 instructions from position 17 +thread #2: tid = 3842850, total instructions = 1000 + would print 12 instructions from position 17''', result.GetOutput()) + + ci.HandleCommand("", result) + self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 + would print 12 instructions from position 29 +thread #2: tid = 3842850, total instructions = 1000 + would print 12 instructions from position 29''', result.GetOutput()) diff --git a/lldb/test/API/commands/trace/TestTraceLoad.py b/lldb/test/API/commands/trace/TestTraceLoad.py --- a/lldb/test/API/commands/trace/TestTraceLoad.py +++ b/lldb/test/API/commands/trace/TestTraceLoad.py @@ -35,6 +35,10 @@ self.assertEqual("6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A", module.GetUUIDString()) + # check that the Process and Thread objects were created correctly + self.expect("thread info", substrs=["tid = 3842849"]) + self.expect("thread list", substrs=["Process 1234 stopped", "tid = 3842849"]) + def testLoadInvalidTraces(self): src_dir = self.getSourceDir() diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json b/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_2threads.json @@ -0,0 +1,35 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 3842849, + "traceFile": "3842849.trace" + }, + { + "tid": 3842850, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +}