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 char *trace_file_path); + #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 trace from trace session file and create Targets based on the + /// contents of the file. + /// + /// \param[out] error + /// An error if the trace could not be created. + /// + /// \param[in] trace_session_file_path + /// The full or partial path of the trace session file describing the + /// trace session. + SBTrace LoadTraceFromFile(SBError &error, const char *trace_file_path); + 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/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,27 @@ SBTrace(const lldb::TraceSP &trace_sp); + /// Load trace from trace session file and create Targets based on the + /// contents of the file. + /// + /// \param[out] error + /// An error if the trace could not be created. + /// + /// \param[in] debugger + /// The debugger instance where new Targets will be created as part of the + /// JSON data parsing. + /// + /// \param[in] trace_session_file_path + /// The full or partial path of the trace session file describing the + /// trace session. static SBTrace LoadTraceFromFile(SBDebugger &debugger, + /// const char *trace_session_file_path); + /// + /// \return + /// A \a TraceSP instance, or an \a llvm::Error if loading the trace + /// from file fails. + static SBTrace LoadTraceFromFile(SBError &error, SBDebugger &debugger, + const char *trace_session_file_path); + /// \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,24 @@ static llvm::Expected FindPluginSchema(llvm::StringRef plugin_name); + /// Load trace from trace session file and create Targets based on the + /// contents of the file. + /// + /// \param[in] debugger + /// The debugger instance where new Targets will be created as part of the + /// JSON data parsing. + /// + /// \param[in] trace_session_file_path + /// The full or partial path of the trace session file describing the + /// trace session. + /// + /// \return + /// A \a TraceSP instance, or an \a llvm::Error if loading the trace + /// from file fails. + static llvm::Expected + LoadPostMortemTraceFromFile(Debugger &debugger, + llvm::StringRef trace_session_file_path); + /// 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,12 @@ if thread is not None: command += " " + str(thread.GetIndexID()) self.expect(command, error=error, substrs=substrs) + + def traceLoad(self, traceSessionFilePath="trace.json", error=False, substrs=None): + if self.USE_SB_API: + loadTraceError = lldb.SBError() + _trace = self.dbg.LoadTraceFromFile(loadTraceError, traceSessionFilePath) + self.assertSBError(loadTraceError, error) + else: + command = f"trace load -v {traceSessionFilePath}" + 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,8 @@ return m_opaque_sp->SetLoggingCallback(log_callback, baton); } } + +SBTrace SBDebugger::LoadTraceFromFile(SBError &error, + const char *trace_file_path) { + return SBTrace::LoadTraceFromFile(error, *this, trace_file_path); +} 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" @@ -26,6 +27,21 @@ LLDB_INSTRUMENT_VA(this, trace_sp); } +SBTrace SBTrace::LoadTraceFromFile(SBError &error, SBDebugger &debugger, + const char *trace_session_file_path) { + + llvm::Expected trace_or_err = + Trace::LoadPostMortemTraceFromFile(debugger.ref(), + trace_session_file_path); + + if (!trace_or_err) { + error.SetErrorString(llvm::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,24 @@ 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; - }; - - FileSpec json_file(command[0].ref()); + llvm::Expected trace_or_err = + Trace::LoadPostMortemTraceFromFile(GetDebugger(), command[0].ref()); - 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()); + lldb::TraceSP trace_sp = trace_or_err.get(); + if (m_options.m_verbose && trace_sp) { + result.AppendMessageWithFormatv("loading trace with plugin {0}\n", + trace_sp->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()); } +llvm::Expected +Trace::LoadPostMortemTraceFromFile(Debugger &debugger, + llvm::StringRef trace_session_file_path) { + + FileSpec json_file(trace_session_file_path); + + auto buffer_or_error = llvm::MemoryBuffer::getFile(json_file.GetPath()); + if (!buffer_or_error) { + return 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()); + } + + llvm::Expected session_file = + json::parse(buffer_or_error.get()->getBuffer().str()); + if (!session_file) { + return session_file.takeError(); + } + + return Trace::FindPluginForPostMortemProcess( + debugger, *session_file, json_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 @@ -9,10 +9,11 @@ mydir = TestBase.compute_mydir(__file__) 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"]) + self.traceLoad(traceSessionFilePath=trace_definition_file, 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", @@ -21,10 +22,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"]) + self.traceLoad(traceSessionFilePath=trace_definition_file, 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", @@ -33,10 +35,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"]) + self.traceLoad(traceSessionFilePath=trace_definition_file, 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", @@ -45,10 +48,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"]) + self.traceLoad(traceSessionFilePath=trace_definition_file, substrs=["intel-pt"]) target = self.dbg.GetSelectedTarget() process = target.GetProcess() @@ -90,11 +94,12 @@ Errors: Number of TSC decoding errors: 0''']) + @testSBAPIAndCommands def testLoadInvalidTraces(self): src_dir = self.getSourceDir() + trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace_bad.json") # 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] + self.traceLoad(traceSessionFilePath=trace_definition_file, error=True, substrs=['''error: expected object at traceSession.processes[0] Context: {