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 @@ -29,6 +29,7 @@ #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" #include "lldb/Target/ThreadSpec.h" +#include "lldb/Target/Trace.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/LLDBAssert.h" @@ -210,7 +211,7 @@ bool GetInjectLocalVariables(ExecutionContext *exe_ctx) const; void SetInjectLocalVariables(ExecutionContext *exe_ctx, bool b); - + void SetRequireHardwareBreakpoints(bool b); bool GetRequireHardwareBreakpoints() const; @@ -1105,6 +1106,29 @@ 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] options + /// The trace dump options. See \a TraceDumpOptions for more information. + void DumpTrace(Stream &s, lldb_private::TraceDumpOptions &options); + // 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 +1418,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 @@ -18,6 +18,17 @@ namespace lldb_private { +/// Helper class that holds the configuration of a Dump action. +/// +/// The \a process field must be set before invoking \a Trace::Dump. +/// +/// If \a tids is empty, then all threads of the given process are considered. +struct TraceDumpOptions { + Process *process = nullptr; + std::set tids; + bool verbose = false; +}; + /// \class Trace Trace.h "lldb/Target/Trace.h" /// A plug-in interface definition class for trace information. /// @@ -33,17 +44,21 @@ /// 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. /// /// 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] options + /// The trace dump options. See \a TraceDumpOptions for more information. + virtual void Dump(lldb_private::Stream &s, + const TraceDumpOptions &options) const = 0; /// Find a trace plug-in using JSON data. /// @@ -135,7 +150,6 @@ std::map> m_thread_to_trace_file_map; - std::vector m_targets; }; } // namespace lldb_private 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 @@ -147,7 +147,16 @@ switch (short_option) { case 'v': { - m_verbose = true; + m_dump_options.verbose = true; + break; + } + case 't': { + lldb::tid_t tid; + if (option_arg.empty() || option_arg.getAsInteger(0, tid)) + error.SetErrorStringWithFormat("invalid thread id string '%s'", + option_arg.str().c_str()); + else + m_dump_options.tids.insert(tid); break; } default: @@ -157,20 +166,21 @@ } void OptionParsingStarting(ExecutionContext *execution_context) override { - m_verbose = false; + m_dump_options = TraceDumpOptions(); } llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_trace_dump_options); } - bool m_verbose; // Enable verbose logging for debugging purposes. + TraceDumpOptions m_dump_options; }; 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 in the current target.", + "trace dump", eCommandRequiresTarget), m_options() {} ~CommandObjectTraceDump() override = default; @@ -180,8 +190,11 @@ protected: bool DoExecute(Args &command, CommandReturnObject &result) override { Status error; - // TODO: fill in the dumping code here! + Target &target = GetSelectedTarget(); + target.DumpTrace(result.GetOutputStream(), m_options.m_dump_options); + 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 @@ -1188,6 +1188,9 @@ 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. Can be " + "repeated multiple times to dump the information of several threads.">; } 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, + const lldb_private::TraceDumpOptions &options) const override; ~TraceIntelPT() override = default; @@ -43,6 +44,11 @@ std::unique_ptr CreateParser() override; private: + void DumpJSONSettings(lldb_private::Stream &s) const; + + void DumpTraceFiles(lldb_private::Stream &s, + const lldb_private::TraceDumpOptions &options) const; + friend class TraceIntelPTSettingsParser; pt_cpu m_pt_cpu; }; 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,16 @@ #include "TraceIntelPT.h" +#include + +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" + #include "TraceIntelPTSettingsParser.h" +#include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" using namespace lldb; using namespace lldb_private; @@ -44,7 +52,98 @@ uint32_t TraceIntelPT::GetPluginVersion() { return 1; } -void TraceIntelPT::Dump(lldb_private::Stream *s) const {} +void DumpImages(Stream &s, const TraceDumpOptions &options) { + s << "\n\nModules:"; + s.IndentMore(); + + Target &target = options.process->GetTarget(); + const ModuleList &modules = target.GetImages(); + + for (size_t i = 0; i < modules.GetSize(); i++) { + const ModuleSP module = modules.GetModuleAtIndex(i); + s << "\n"; + s.Indent(); + if (module->GetUUID().IsValid()) { + s << "uuid: "; + module->GetUUID().Dump(&s); + s << ", "; + } + + if (ObjectFile *objfile = module->GetObjectFile()) { + Address base_addr(objfile->GetBaseAddress()); + if (base_addr.IsValid()) { + s << "load address: "; + if (!target.GetSectionLoadList().IsEmpty()) { + lldb::addr_t load_addr = base_addr.GetLoadAddress(&target); + if (load_addr == LLDB_INVALID_ADDRESS) { + base_addr.Dump(&s, &target, Address::DumpStyleModuleWithFileAddress, + Address::DumpStyleFileAddress); + } else { + // Show the load address of the image + s.Printf("0x%" PRIx64, load_addr); + } + } else { + // The address was valid, but the image isn't loaded, output the + // address in an appropriate format + base_addr.Dump(&s, &target, Address::DumpStyleFileAddress); + } + s << ", "; + } + } + s << "file: "; + module->GetFileSpec().Dump(s.AsRawOstream()); + } + + s.IndentLess(); +} + +void TraceIntelPT::DumpJSONSettings(Stream &s) const { + s << "Settings:\n"; + 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 << "\n\nSettings directory:\n"; + s.IndentMore(); + s.Indent(); + s << m_settings_dir << "\n\n"; + s.IndentLess(); +} + +void TraceIntelPT::DumpTraceFiles(Stream &s, + const TraceDumpOptions &options) const { + s << "Trace files:"; + + lldb::pid_t pid = options.process->GetID(); + const auto &process_it = m_thread_to_trace_file_map.find(pid); + if (process_it == m_thread_to_trace_file_map.end()) + return; + + s.IndentMore(); + for (const auto &thread_trace_file : process_it->second) { + lldb::tid_t tid = thread_trace_file.first; + if (options.tids.empty() || options.tids.count(tid)) { + s << "\n"; + s.Indent(); + s.Printf("pid: '%" PRIu64 "', tid: '%" PRIu64 "', trace file: ", pid, + tid); + s << thread_trace_file.second; + } + } + s.IndentLess(); +} + +void TraceIntelPT::Dump(Stream &s, const TraceDumpOptions &options) const { + assert(options.process != nullptr && "The process field must be specified"); + if (options.verbose) + DumpJSONSettings(s); + + DumpTraceFiles(s, options); + DumpImages(s, options); +} lldb::TraceSP TraceIntelPT::CreateInstance() { return lldb::TraceSP(new TraceIntelPT()); 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,31 @@ 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, TraceDumpOptions &options) { + if (!m_trace_sp) { + s << "error: no trace data in this target"; + return; + } + if (!m_process_sp) { + s << "error: no process in this target"; + return; + } + options.process = m_process_sp.get(); + ThreadSP thread_sp; + for (lldb::tid_t tid : options.tids) { + if (!m_process_sp->GetThreadList().FindThreadByID(tid)) { + s.Printf("error: the thread id \"%" PRIu64 + "\" does not exist in this target", + tid); + } + } + m_trace_sp->Dump(s, options); +} + 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 @@ -13,6 +13,7 @@ #include "llvm/Support/Format.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Target/Target.h" using namespace lldb; using namespace lldb_private; @@ -85,11 +86,7 @@ llvm::Error Trace::ParseSettings(Debugger &debugger, const llvm::json::Value &settings, llvm::StringRef settings_dir) { - if (llvm::Error err = - CreateParser()->ParseSettings(debugger, settings, settings_dir)) - return err; - - return llvm::Error::success(); + return CreateParser()->ParseSettings(debugger, settings, settings_dir); } llvm::StringRef Trace::GetSchema() { return CreateParser()->GetSchema(); } diff --git a/lldb/source/Target/TraceSettingsParser.cpp b/lldb/source/Target/TraceSettingsParser.cpp --- a/lldb/source/Target/TraceSettingsParser.cpp +++ b/lldb/source/Target/TraceSettingsParser.cpp @@ -166,10 +166,12 @@ return err; } + for (auto target_sp : m_targets) + target_sp->SetTrace(m_trace.shared_from_this()); + m_trace.m_settings = *raw_settings.getAsObject(); m_trace.m_settings_dir = m_settings_dir; m_trace.m_thread_to_trace_file_map = m_thread_to_trace_file_map; - m_trace.m_targets = m_targets; return llvm::Error::success(); } 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,49 @@ +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") + self.src_dir = self.getSourceDir() + + def testDumpMissingTrace(self): + self.expect("target create " + os.path.join(self.src_dir, 'intelpt-trace', 'a.out')) + self.expect("trace dump", substrs=["error: no trace data in this target"]) + + def testDumpTrace(self): + self.expect("trace load " + os.path.join(self.src_dir, "intelpt-trace", "trace2.json")) + + # 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', trace file: .*/test/API/commands/trace/intelpt-trace/3842849.trace", + "Modules:\n uuid: 6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A, load address: 0x0000000000400000, file: .*intelpt-trace/a.out"]) + + # 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', trace file: .*/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', trace file: .*/test/API/commands/trace/intelpt-trace/3842849.trace", + "pid: '2', tid: '22', trace file: .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + + self.expect("trace dump -t 21 -t 22", substrs=["Trace files"], + patterns=["pid: '2', tid: '21', trace file: .*/test/API/commands/trace/intelpt-trace/3842849.trace", + "pid: '2', tid: '22', trace file: .*/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', trace file: .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) 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" + } + ] + } + ] +}