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 @@ -28,6 +28,7 @@ #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" +#include "lldb/Target/Trace.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/LLDBAssert.h" @@ -209,7 +210,7 @@ bool GetInjectLocalVariables(ExecutionContext *exe_ctx) const; void SetInjectLocalVariables(ExecutionContext *exe_ctx, bool b); - + void SetRequireHardwareBreakpoints(bool b); bool GetRequireHardwareBreakpoints() const; @@ -1102,6 +1103,33 @@ 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(); + + /// Dump the \a Trace information. + /// + /// \param[in] s + /// The stream object where the information is printed. + /// + /// \param[in] tid + /// If tid is not \a LLDB_INVALID_THREAD_ID, then only the trace information + /// of the provided thread is printed. + /// + /// \param[in] verbose + /// Flag that indicates to print verbose information. + void DumpTrace(Stream &s, lldb::tid_t tid, bool verbose); + // 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 @@ -1294,12 +1322,12 @@ lldb::PlatformSP m_platform_sp; ///< The platform for this target. std::recursive_mutex m_mutex; ///< An API mutex that is used by the lldb::SB* /// classes make the SB interface thread safe - /// When the private state thread calls SB API's - usually because it is + /// When the private state thread calls SB API's - usually because it is /// running OS plugin or Python ThreadPlan code - it should not block on the /// API mutex that is held by the code that kicked off the sequence of events - /// that led us to run the code. We hand out this mutex instead when we + /// that led us to run the code. We hand out this mutex instead when we /// detect that code is running on the private state thread. - std::recursive_mutex m_private_mutex; + std::recursive_mutex m_private_mutex; Arch m_arch; ModuleList m_images; ///< The list of images for this process (shared /// libraries and anything dynamically loaded). @@ -1334,6 +1362,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/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -33,19 +33,32 @@ /// 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: ~Trace() override = default; /// Dump the trace data that this plug-in has access to. /// /// This function will dump all of the trace data for all threads in a user - /// readable format. Options for dumping can be added as this API is iterated - /// on. + /// readable format. /// /// \param[in] s /// A stream object to dump the information to. - virtual void Dump(Stream *s) const = 0; + /// + /// \param[in] process + /// The process to show the trace information of. If it is \a nullptr, + /// then all processes of the current target will be considered. + /// + /// \param[in] thread + /// The thread of the \a process above to show the trace information of. + /// If it is \a nullptr, then all threads of the given process will be + /// considered. + /// + /// \param[in] verbose + /// Flag that indicates to print verbose information. + virtual void Dump(lldb_private::Stream &s, lldb_private::Process *process, + lldb_private::Thread *thread, bool verbose) const = 0; /// Find a trace plug-in using JSON data. /// diff --git a/lldb/source/Commands/CommandObjectTrace.cpp b/lldb/source/Commands/CommandObjectTrace.cpp --- a/lldb/source/Commands/CommandObjectTrace.cpp +++ b/lldb/source/Commands/CommandObjectTrace.cpp @@ -152,6 +152,12 @@ m_verbose = true; break; } + case 't': { + if (option_arg.empty() || option_arg.getAsInteger(0, m_tid)) + error.SetErrorStringWithFormat("invalid thread id string '%s'", + option_arg.str().c_str()); + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -160,6 +166,7 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_verbose = false; + m_tid = LLDB_INVALID_THREAD_ID; } llvm::ArrayRef GetDefinitions() override { @@ -167,12 +174,14 @@ } bool m_verbose; // Enable verbose logging for debugging purposes. + tid_t m_tid; }; CommandObjectTraceDump(CommandInterpreter &interpreter) - : CommandObjectParsed(interpreter, "trace dump", - "Dump the loaded processor trace data.", - "trace dump"), + : CommandObjectParsed( + interpreter, "trace dump", + "Dump the loaded processor trace data from the current target.", + "trace dump"), m_options() {} ~CommandObjectTraceDump() override = default; @@ -182,8 +191,12 @@ protected: bool DoExecute(Args &command, CommandReturnObject &result) override { Status error; - // TODO: fill in the dumping code here! + Target &target = GetSelectedOrDummyTarget(); + target.DumpTrace(result.GetOutputStream(), m_options.m_tid, + m_options.m_verbose); + if (error.Success()) { + result.AppendMessage("\n"); result.SetStatus(eReturnStatusSuccessFinishResult); } else { result.AppendErrorWithFormat("%s\n", error.AsCString()); 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 @@ -1171,6 +1171,8 @@ let Command = "trace dump" in { def trace_dump_verbose : Option<"verbose", "v">, Group<1>, Desc<"Show verbose trace information.">; + def trace_dump_thread_id : Option<"thread-id", "t">, Group<1>, + Arg<"ThreadID">, Desc<"The thread id to dump trace information of.">; } let Command = "trace schema" in { 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 @@ -18,7 +18,8 @@ class TraceIntelPT : public lldb_private::Trace { public: - void Dump(lldb_private::Stream *s) const override; + void Dump(lldb_private::Stream &s, lldb_private::Process *process, + lldb_private::Thread *thread, bool verbose) const override; llvm::StringRef GetSchema() override; 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 @@ -8,8 +8,12 @@ #include "TraceIntelPT.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" + #include "TraceIntelPTSettingsParser.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" using namespace lldb; using namespace lldb_private; @@ -54,7 +58,40 @@ uint32_t TraceIntelPT::GetPluginVersion() { return 1; } -void TraceIntelPT::Dump(lldb_private::Stream *s) const {} +void TraceIntelPT::Dump(Stream &s, Process *process, Thread *thread, + bool verbose) const { + if (verbose) { + s << "Settings:\n"; + s.Indent(); + std::string str; + llvm::raw_string_ostream OS(str); + json::Object obj = m_settings; + OS << llvm::formatv("{0:2}", json::Value(std::move(obj))); + OS.flush(); + s << OS.str(); + s.IndentLess(); + + s << "\n\nSettings directory:\n"; + s.Indent(); + s << m_settings_dir << "\n\n"; + s.IndentLess(); + } + s << "Trace files:"; + + // We go through all the processes and threads even when there are filters as + // a way to simplify the implementation. + for (const auto &process_it : m_thread_to_trace_file_map) { + if (!process || process->GetID() == process_it.first) { + for (const auto &thread_it : process_it.second) { + if (!thread || thread->GetID() == thread_it.first) { + s.Printf("\npid: '%" PRIu64 "', tid: '%" PRIu64 "' -> ", + process_it.first, thread_it.first); + s << thread_it.second; + } + } + } + } +} lldb::TraceSP TraceIntelPT::CreateInstance(const llvm::json::Object &settings, StringRef settings_dir) { 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,32 @@ return error; } +void Target::SetTrace(const lldb::TraceSP &trace_sp) { m_trace_sp = trace_sp; } + +lldb::TraceSP &Target::GetTrace() { return m_trace_sp; } + +void Target::DumpTrace(Stream &s, lldb::tid_t tid, bool verbose) { + if (!m_trace_sp) { + s << "error: no trace data in this target"; + return; + } + if (!m_process_sp) { + s << "error: no processes in this target"; + return; + } + ThreadSP thread_sp; + if (tid != LLDB_INVALID_THREAD_ID) { + thread_sp = m_process_sp->GetThreadList().FindThreadByID(tid); + if (!thread_sp) { + s.Printf("error: the thread id \"%" PRIu64 + "\" does not exist in this target", + tid); + return; + } + } + m_trace_sp->Dump(s, m_process_sp.get(), thread_sp.get(), verbose); +} + Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { auto state = eStateInvalid; auto process_sp = GetProcessSP(); diff --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp --- a/lldb/source/Target/Trace.cpp +++ b/lldb/source/Target/Trace.cpp @@ -14,6 +14,7 @@ #include "llvm/Support/Format.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Target/Target.h" using namespace lldb; using namespace lldb_private; @@ -52,6 +53,9 @@ m_thread_to_trace_file_map = std::move(parser.get()->m_thread_to_trace_file_map); + for (auto target_sp : parser.get()->m_targets) + target_sp->SetTrace(shared_from_this()); + return llvm::Error::success(); } else return parser.takeError(); diff --git a/lldb/test/API/commands/trace/TestTraceDump.py b/lldb/test/API/commands/trace/TestTraceDump.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceDump.py @@ -0,0 +1,44 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceDump(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 testDumpTrace(self): + self.expect("trace dump", substrs=["error: no trace data in this target"]) + + src_dir = self.getSourceDir() + trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace2.json") + self.expect("trace load " + trace_definition_file) + + # An invalid thread id should show an error message + self.expect("trace dump -t 5678", substrs=['error: the thread id "5678" does not exist in this target']) + + # Only the specified thread should be printed + self.expect("trace dump -t 22", substrs=["Trace files"], + patterns=["pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + + # Verbose should output the entire JSON settings besides the thread specific information + self.expect("trace dump -t 22 -v", + substrs=["Settings", "3842849.trace", "intel-pt", "Settings directory"], + patterns=["pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + + # In this case all threads should be printed + self.expect("trace dump", substrs=["Trace files"], + patterns=["pid: '2', tid: '21' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace", + "pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + + # We switch targets and check the threads of this target + self.dbg.SetSelectedTarget(self.dbg.FindTargetWithProcessID(1)) + self.expect("trace dump", substrs=["Trace files"], + patterns=["pid: '1', tid: '11' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) 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 @@ -55,3 +55,5 @@ self.expect("trace load -v " + trace_definition_file2, error=True, substrs=['error: JSON object is missing the "pid" field.']) self.assertEqual(self.dbg.GetNumTargets(), 0) + + self.expect("trace dump", substrs=["error: no trace data in this target"]) diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace2.json b/lldb/test/API/commands/trace/intelpt-trace/trace2.json new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace2.json @@ -0,0 +1,53 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 11, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + }, + { + "pid": 2, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 21, + "traceFile": "3842849.trace" + }, + { + "tid": 22, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +}