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 @@ -2165,6 +2165,130 @@ ~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 = 10; + m_start_position = 0; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_thread_trace_dump_instructions_options); + } + + // 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() {} + + ~CommandObjectTraceDumpInstructions() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + 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, + m_options.m_start_position); + return true; + } + + CommandOptions m_options; +}; + +// 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 +2364,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. 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,82 @@ +//===-- 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" + +struct ThreadData; + +class ProcessTrace : public lldb_private::Process { +public: + static lldb::ProcessSP + CreateInstance(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, + const lldb_private::FileSpec *crash_file_path); + + static void Initialize(); + + static void Terminate(); + + static lldb_private::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(lldb_private::ArchSpec &process_arch) override; + + lldb_private::DynamicLoader *GetDynamicLoader() override { return nullptr; } + + lldb_private::SystemRuntime *GetSystemRuntime() override { return nullptr; } + + lldb_private::ConstString GetPluginName() override; + + uint32_t GetPluginVersion() override; + + lldb_private::Status DoDestroy() override; + + void RefreshStateAfterStop() override; + + lldb_private::Status WillResume() override { + lldb_private::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, + lldb_private::Status &error) override; + + size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size, + lldb_private::Status &error) override; + + lldb_private::ArchSpec GetArchitecture(); + + bool GetProcessInfo(lldb_private::ProcessInstanceInfo &info) override; + +protected: + void Clear(); + + bool UpdateThreadList(lldb_private::ThreadList &old_thread_list, + lldb_private::ThreadList &new_thread_list) override; +}; + +#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,129 @@ +//===-- 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; + +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); +} + +lldb::ProcessSP ProcessTrace::CreateInstance(lldb::TargetSP target_sp, + lldb::ListenerSP listener_sp, + const FileSpec *crash_file) { + return std::make_shared(target_sp, listener_sp); +} + +bool ProcessTrace::CanDebug(lldb::TargetSP target_sp, + bool plugin_specified_by_name) { + return plugin_specified_by_name; +} + +ProcessTrace::ProcessTrace(lldb::TargetSP target_sp, + lldb::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(lldb::eStateStopped); + + lldb::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(lldb::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()); + lldb::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(lldb::addr_t addr, void *buf, size_t size, + Status &error) { + return 0; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionParser.cpp @@ -107,7 +107,7 @@ m_debugger.GetTargetList().SetSelectedTarget(target_sp.get()); ProcessSP process_sp(target_sp->CreateProcess( - /*listener*/ nullptr, /*plugin_name*/ llvm::StringRef(), + /*listener*/ nullptr, "trace", /*crash_file*/ nullptr)); process_sp->SetID(static_cast(process.pid)); @@ -118,6 +118,15 @@ if (llvm::Error err = ParseModule(target_sp, module)) return err; } + + if (!process.threads.empty()) + process_sp->GetThreadList().SetSelectedThreadByIndexID(0); + + // We invoke DidAttach to create a correct stopped state for the process and + // its threads. + lldb_private::ArchSpec process_arch; + process_sp->DidAttach(process_arch); + return llvm::Error::success(); } @@ -164,5 +173,9 @@ return std::move(err); } - return TraceSP(new TraceIntelPT(m_pt_cpu, m_targets)); + TraceSP trace_instance(new TraceIntelPT(m_pt_cpu, m_targets)); + for (auto target_sp : m_targets) + target_sp->SetTrace(trace_instance->shared_from_this()); + + return trace_instance; } 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 lldb::TraceSP &trace_sp) { m_trace_sp = trace_sp; } + +lldb::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,17 @@ 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 "\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,56 @@ +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', + 'would print 10 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', + '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', + 'would print 10 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"']) 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()