diff --git a/lldb/bindings/interface/SBDebugger.i b/lldb/bindings/interface/SBDebugger.i --- a/lldb/bindings/interface/SBDebugger.i +++ b/lldb/bindings/interface/SBDebugger.i @@ -542,6 +542,8 @@ lldb::SBError RunREPL (lldb::LanguageType language, const char *repl_options); + SBTrace LoadTraceFromFile(SBError &error, const SBFileSpec &trace_description_file); + #ifdef SWIGPYTHON %pythoncode%{ def __iter__(self): diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -391,6 +391,17 @@ SBError RunREPL(lldb::LanguageType language, const char *repl_options); + /// Load a trace from a trace description file and create Targets, + /// Processes and Threads based on the contents of such file. + /// + /// \param[out] error + /// An error if the trace could not be created. + /// + /// \param[in] trace_description_file + /// The file containing the necessary information to load the trace. + SBTrace LoadTraceFromFile(SBError &error, + const SBFileSpec &trace_description_file); + private: friend class SBCommandInterpreter; friend class SBInputReader; @@ -398,6 +409,7 @@ friend class SBProcess; friend class SBSourceManager; friend class SBTarget; + friend class SBTrace; lldb::SBTarget FindTargetWithLLDBProcess(const lldb::ProcessSP &processSP); diff --git a/lldb/include/lldb/API/SBFileSpec.h b/lldb/include/lldb/API/SBFileSpec.h --- a/lldb/include/lldb/API/SBFileSpec.h +++ b/lldb/include/lldb/API/SBFileSpec.h @@ -74,6 +74,7 @@ friend class SBSourceManager; friend class SBTarget; friend class SBThread; + friend class SBTrace; SBFileSpec(const lldb_private::FileSpec &fspec); diff --git a/lldb/include/lldb/API/SBTrace.h b/lldb/include/lldb/API/SBTrace.h --- a/lldb/include/lldb/API/SBTrace.h +++ b/lldb/include/lldb/API/SBTrace.h @@ -12,8 +12,6 @@ #include "lldb/API/SBDefines.h" #include "lldb/API/SBError.h" -class TraceImpl; - namespace lldb { class LLDB_API SBTrace { @@ -23,6 +21,10 @@ SBTrace(const lldb::TraceSP &trace_sp); + /// See SBDebugger::LoadTraceFromFile. + static SBTrace LoadTraceFromFile(SBError &error, SBDebugger &debugger, + const SBFileSpec &trace_description_file); + /// \return /// A description of the parameters to use for the \a SBTrace::Start /// method, or \b null if the object is invalid. 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 @@ -137,6 +137,23 @@ static llvm::Expected FindPluginSchema(llvm::StringRef plugin_name); + /// Load a trace from a trace description file and create Targets, + /// Processes and Threads based on the contents of such file. + /// + /// \param[in] debugger + /// The debugger instance where new Targets will be created as part of the + /// JSON data parsing. + /// + /// \param[in] trace_description_file + /// The file containing the necessary information to load the trace. + /// + /// \return + /// A \a TraceSP instance, or an \a llvm::Error if loading the trace + /// fails. + static llvm::Expected + LoadPostMortemTraceFromFile(Debugger &debugger, + const FileSpec &trace_description_file); + /// Get the command handle for the "process trace start" command. virtual lldb::CommandObjectSP GetProcessTraceStartCommand(CommandInterpreter &interpreter) = 0; diff --git a/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/intelpt/intelpt_testcase.py @@ -133,3 +133,13 @@ if thread is not None: command += " " + str(thread.GetIndexID()) self.expect(command, error=error, substrs=substrs) + + def traceLoad(self, traceDescriptionFilePath="trace.json", error=False, substrs=None): + if self.USE_SB_API: + traceDescriptionFile = lldb.SBFileSpec(traceDescriptionFilePath, True) + loadTraceError = lldb.SBError() + _trace = self.dbg.LoadTraceFromFile(loadTraceError, traceDescriptionFile) + self.assertSBError(loadTraceError, error) + else: + command = f"trace load -v {traceDescriptionFilePath}" + self.expect(command, error=error, substrs=substrs) diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -27,6 +27,7 @@ #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" +#include "lldb/API/SBTrace.h" #include "lldb/API/SBTypeCategory.h" #include "lldb/API/SBTypeFilter.h" #include "lldb/API/SBTypeFormat.h" @@ -1633,3 +1634,10 @@ return m_opaque_sp->SetLoggingCallback(log_callback, baton); } } + +SBTrace +SBDebugger::LoadTraceFromFile(SBError &error, + const SBFileSpec &trace_description_file) { + LLDB_INSTRUMENT_VA(this, error, trace_description_file); + return SBTrace::LoadTraceFromFile(error, *this, trace_description_file); +} diff --git a/lldb/source/API/SBTrace.cpp b/lldb/source/API/SBTrace.cpp --- a/lldb/source/API/SBTrace.cpp +++ b/lldb/source/API/SBTrace.cpp @@ -9,6 +9,7 @@ #include "lldb/Target/Process.h" #include "lldb/Utility/Instrumentation.h" +#include "lldb/API/SBDebugger.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBThread.h" #include "lldb/API/SBTrace.h" @@ -19,6 +20,7 @@ using namespace lldb; using namespace lldb_private; +using namespace llvm; SBTrace::SBTrace() { LLDB_INSTRUMENT_VA(this); } @@ -26,6 +28,21 @@ LLDB_INSTRUMENT_VA(this, trace_sp); } +SBTrace SBTrace::LoadTraceFromFile(SBError &error, SBDebugger &debugger, + const SBFileSpec &trace_description_file) { + LLDB_INSTRUMENT_VA(error, debugger, trace_description_file); + + Expected trace_or_err = Trace::LoadPostMortemTraceFromFile( + debugger.ref(), trace_description_file.ref()); + + if (!trace_or_err) { + error.SetErrorString(toString(trace_or_err.takeError()).c_str()); + return SBTrace(); + } + + return SBTrace(trace_or_err.get()); +} + const char *SBTrace::GetStartConfigurationHelp() { LLDB_INSTRUMENT_VA(this); return m_opaque_sp ? m_opaque_sp->GetStartConfigurationHelp() : nullptr; 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 @@ -85,41 +85,26 @@ if (command.size() != 1) { result.AppendError( "a single path to a JSON file containing a trace session" - "is required"); + " is required"); return false; } - auto end_with_failure = [&result](llvm::Error err) -> bool { - result.AppendErrorWithFormat("%s\n", - llvm::toString(std::move(err)).c_str()); - return false; - }; + const FileSpec trace_description_file(command[0].ref()); - FileSpec json_file(command[0].ref()); + llvm::Expected trace_or_err = + Trace::LoadPostMortemTraceFromFile(GetDebugger(), + trace_description_file); - auto buffer_or_error = llvm::MemoryBuffer::getFile(json_file.GetPath()); - if (!buffer_or_error) { - return end_with_failure(llvm::createStringError( - std::errc::invalid_argument, "could not open input file: %s - %s.", - json_file.GetPath().c_str(), - buffer_or_error.getError().message().c_str())); + if (!trace_or_err) { + result.AppendErrorWithFormat( + "%s\n", llvm::toString(trace_or_err.takeError()).c_str()); + return false; } - llvm::Expected session_file = - json::parse(buffer_or_error.get()->getBuffer().str()); - if (!session_file) - return end_with_failure(session_file.takeError()); - - if (Expected traceOrErr = - Trace::FindPluginForPostMortemProcess( - GetDebugger(), *session_file, - json_file.GetDirectory().AsCString())) { - lldb::TraceSP trace_sp = traceOrErr.get(); - if (m_options.m_verbose && trace_sp) - result.AppendMessageWithFormatv("loading trace with plugin {0}\n", - trace_sp->GetPluginName()); - } else - return end_with_failure(traceOrErr.takeError()); + if (m_options.m_verbose) { + result.AppendMessageWithFormatv("loading trace with plugin {0}\n", + trace_or_err.get()->GetPluginName()); + } result.SetStatus(eReturnStatusSuccessFinishResult); return true; 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 @@ -89,6 +89,30 @@ plugin_name.data()); } +Expected +Trace::LoadPostMortemTraceFromFile(Debugger &debugger, + const FileSpec &trace_description_file) { + + auto buffer_or_error = + MemoryBuffer::getFile(trace_description_file.GetPath()); + if (!buffer_or_error) { + return createStringError(std::errc::invalid_argument, + "could not open input file: %s - %s.", + trace_description_file.GetPath().c_str(), + buffer_or_error.getError().message().c_str()); + } + + Expected session_file = + json::parse(buffer_or_error.get()->getBuffer().str()); + if (!session_file) { + return session_file.takeError(); + } + + return Trace::FindPluginForPostMortemProcess( + debugger, *session_file, + trace_description_file.GetDirectory().AsCString()); +} + Expected Trace::FindPluginForPostMortemProcess(Debugger &debugger, const json::Value &trace_session_file, 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 @@ -7,10 +7,11 @@ class TestTraceLoad(TraceIntelPTTestCaseBase): NO_DEBUG_INFO_TESTCASE = True + @testSBAPIAndCommands def testLoadMultiCoreTrace(self): src_dir = self.getSourceDir() - trace_definition_file = os.path.join(src_dir, "intelpt-multi-core-trace", "trace.json") - self.expect("trace load -v " + trace_definition_file, substrs=["intel-pt"]) + trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace.json") + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) self.expect("thread trace dump instructions 2 -t", substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event", "m.out`foo() + 65 at multi_thread.cpp:12:21", @@ -19,10 +20,11 @@ substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", "m.out`bar() + 26 at multi_thread.cpp:20:6"]) + @testSBAPIAndCommands def testLoadMultiCoreTraceWithStringNumbers(self): src_dir = self.getSourceDir() - trace_definition_file = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_with_string_numbers.json") - self.expect("trace load -v " + trace_definition_file, substrs=["intel-pt"]) + trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_with_string_numbers.json") + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) self.expect("thread trace dump instructions 2 -t", substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event", "m.out`foo() + 65 at multi_thread.cpp:12:21", @@ -31,10 +33,11 @@ substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", "m.out`bar() + 26 at multi_thread.cpp:20:6"]) + @testSBAPIAndCommands def testLoadMultiCoreTraceWithMissingThreads(self): src_dir = self.getSourceDir() - trace_definition_file = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_missing_threads.json") - self.expect("trace load -v " + trace_definition_file, substrs=["intel-pt"]) + trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace_missing_threads.json") + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) self.expect("thread trace dump instructions 3 -t", substrs=["19521: [tsc=0x008fb5211c143fd8] error: expected tracing enabled event", "m.out`foo() + 65 at multi_thread.cpp:12:21", @@ -43,10 +46,11 @@ substrs=["67910: [tsc=0x008fb5211bfdf270] 0x0000000000400bd7 addl $0x1, -0x4(%rbp)", "m.out`bar() + 26 at multi_thread.cpp:20:6"]) + @testSBAPIAndCommands def testLoadTrace(self): src_dir = self.getSourceDir() - trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace.json") - self.expect("trace load -v " + trace_definition_file, substrs=["intel-pt"]) + trace_description_file_path = os.path.join(src_dir, "intelpt-trace", "trace.json") + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, substrs=["intel-pt"]) target = self.dbg.GetSelectedTarget() process = target.GetProcess() @@ -88,11 +92,13 @@ Errors: Number of TSC decoding errors: 0''']) + @testSBAPIAndCommands def testLoadInvalidTraces(self): src_dir = self.getSourceDir() + # We test first an invalid type - self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad.json"), error=True, - substrs=['''error: expected object at traceSession.processes[0] + trace_description_file_path = os.path.join(src_dir, "intelpt-trace", "trace_bad.json") + expected_substrs = ['''error: expected object at traceSession.processes[0] Context: { @@ -114,15 +120,19 @@ "family": integer, "model": integer, "stepping": integer - },''']) + },'''] + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs) + # Now we test a wrong cpu family field in the global session file - self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad2.json"), error=True, - substrs=['error: expected uint64_t at traceSession.cpuInfo.family', "Context", "Schema"]) + trace_description_file_path = os.path.join(src_dir, "intelpt-trace", "trace_bad2.json") + expected_substrs = ['error: expected uint64_t at traceSession.cpuInfo.family', "Context", "Schema"] + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs) + # Now we test a missing field in the intel-pt settings - self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad4.json"), error=True, - substrs=['''error: missing value at traceSession.cpuInfo.family + trace_description_file_path = os.path.join(src_dir, "intelpt-trace", "trace_bad4.json") + expected_substrs = ['''error: missing value at traceSession.cpuInfo.family Context: { @@ -133,15 +143,20 @@ }, "processes": [], "type": "intel-pt" -}''', "Schema"]) +}''', "Schema"] + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs) + # Now we test an incorrect load address in the intel-pt settings - self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad5.json"), error=True, - substrs=['error: missing value at traceSession.processes[1].pid', "Schema"]) + trace_description_file_path = os.path.join(src_dir, "intelpt-trace", "trace_bad5.json") + expected_substrs = ['error: missing value at traceSession.processes[1].pid', "Schema"] + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs) + # The following wrong schema will have a valid target and an invalid one. In the case of failure, # no targets should be created. self.assertEqual(self.dbg.GetNumTargets(), 0) - self.expect("trace load -v " + os.path.join(src_dir, "intelpt-trace", "trace_bad3.json"), error=True, - substrs=['error: missing value at traceSession.processes[1].pid']) + trace_description_file_path = os.path.join(src_dir, "intelpt-trace", "trace_bad3.json") + expected_substrs = ['error: missing value at traceSession.processes[1].pid'] + self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs) self.assertEqual(self.dbg.GetNumTargets(), 0)