Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ lldb/include/lldb/Target/Trace.h @@ -20,6 +20,7 @@ #include "lldb/Utility/TraceGDBRemotePackets.h" #include "lldb/Utility/UnimplementedError.h" #include "lldb/lldb-private.h" +#include "lldb/lldb-types.h" namespace lldb_private { @@ -55,6 +56,23 @@ /// A stream object to dump the information to. virtual void Dump(Stream *s) const = 0; + /// Save the trace of a live process to the specified directory, which + /// will be created if needed. + /// This will also create a a file \a /trace.json with the main + /// properties of the trace session, along with others files which contain + /// the actual trace data. The trace.json file can be used later as input + /// for the "trace load" command to load the trace in LLDB. + /// The process being trace is not a live process, return an error. + /// + /// \param[in] directory + /// The directory where the trace files will be saved. + /// + /// \return + /// \a llvm::success if the operation was successful, or an \a llvm::Error + /// otherwise. + /// + virtual llvm::Error SaveLiveTraceToDisk(FileSpec directory) = 0; + /// Find a trace plug-in using JSON data. /// /// When loading trace data from disk, the information for the trace data @@ -156,12 +174,13 @@ /// Check if a thread is currently traced by this object. /// - /// \param[in] thread + /// \param[in] tid /// The thread in question. /// /// \return /// \b true if the thread is traced by this instance, \b false otherwise. - virtual bool IsTraced(const Thread &thread) = 0; + /// + virtual bool IsTraced(lldb::tid_t tid) = 0; /// \return /// A description of the parameters to use for the \a Trace::Start method. Index: lldb/source/Commands/CommandObjectProcess.cpp =================================================================== --- lldb/source/Commands/CommandObjectProcess.cpp +++ lldb/source/Commands/CommandObjectProcess.cpp @@ -1641,6 +1641,80 @@ } }; +// CommandObjectProcessTraceSave +#define LLDB_OPTIONS_process_trace_save +#include "CommandOptions.inc" + +#pragma mark CommandObjectProcessTraceSave + +class CommandObjectProcessTraceSave : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + 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 'd': { + m_directory.SetFile(option_arg, FileSpec::Style::native); + FileSystem::Instance().Resolve(m_directory); + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override{}; + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_process_trace_save_options); + }; + + FileSpec m_directory; + }; + + Options *GetOptions() override { return &m_options; } + CommandObjectProcessTraceSave(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process trace save", + "Save the trace of the current process in the specified directory. " + "The directory will be created if needed. " + "This will also create a file /trace.json with the main " + "properties of the trace session, along with others files which " + "contain the actual trace data. The trace.json file can be used " + "later as input for the \"trace load\" command to load the trace " + "in LLDB", + "process trace save []", + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | + eCommandProcessMustBeTraced) {} + + ~CommandObjectProcessTraceSave() override = default; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + ProcessSP process_sp = m_exe_ctx.GetProcessSP(); + + TraceSP trace_sp = process_sp->GetTarget().GetTrace(); + + if (llvm::Error err = trace_sp->SaveLiveTraceToDisk(m_options.m_directory)) + result.AppendError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); + } + + CommandOptions m_options; +}; + // CommandObjectProcessTraceStop class CommandObjectProcessTraceStop : public CommandObjectParsed { public: @@ -1678,6 +1752,8 @@ : CommandObjectMultiword( interpreter, "trace", "Commands for tracing the current process.", "process trace []") { + LoadSubCommand("save", CommandObjectSP( + new CommandObjectProcessTraceSave(interpreter))); LoadSubCommand("start", CommandObjectSP(new CommandObjectProcessTraceStart( interpreter))); LoadSubCommand("stop", CommandObjectSP( Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -736,6 +736,16 @@ "of corefile to be saved.">; } + +let Command = "process trace save" in { + def process_trace_save_directory: Option<"directory", "d">, + Group<1>, + Arg<"Value">, Required, + Desc<"The directory where the trace will be saved." + "It will be created if it does not exist.">; +} + + let Command = "script import" in { def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>, Desc<"Allow the script to be loaded even if it was already loaded before. " Index: lldb/source/Plugins/Trace/common/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Trace/common/CMakeLists.txt +++ lldb/source/Plugins/Trace/common/CMakeLists.txt @@ -1,6 +1,8 @@ add_lldb_library(lldbPluginTraceCommon ThreadPostMortemTrace.cpp + TraceJSONStructs.cpp TraceSessionFileParser.cpp + TraceSessionSaver.cpp LINK_LIBS lldbCore Index: lldb/source/Plugins/Trace/common/TraceJSONStructs.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/common/TraceJSONStructs.h @@ -0,0 +1,126 @@ +//===-- TraceJSONStruct.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_TARGET_TRACEJSONSTRUCTS_H +#define LLDB_TARGET_TRACEJSONSTRUCTS_H + +#include "lldb/lldb-types.h" +#include "llvm/Support/JSON.h" + +namespace lldb_private { + +/// C++ structs representing the JSON trace session. +/// \{ +struct JSONAddress { + JSONAddress() = default; + + JSONAddress(lldb::addr_t value) : value(value){}; + + lldb::addr_t value; +}; + +struct JSONModule { + JSONModule() = default; + + JSONModule(std::string system_path, llvm::Optional file, + JSONAddress load_address, llvm::Optional uuid) + : system_path(system_path), file(file), load_address(load_address), + uuid(uuid) {} + + std::string system_path; + llvm::Optional file; + JSONAddress load_address; + llvm::Optional uuid; +}; + +struct JSONThread { + JSONThread() = default; + + JSONThread(int64_t tid, std::string trace_file) + : tid(tid), trace_file(trace_file) {} + + int64_t tid; + std::string trace_file; +}; + +struct JSONProcess { + JSONProcess() = default; + + JSONProcess(int64_t pid, std::string triple, std::vector threads, + std::vector modules) + : pid(pid), triple(triple), threads(threads), modules(modules) {} + + int64_t pid; + std::string triple; + std::vector threads; + std::vector modules; +}; + +struct JSONTracePluginSettings { + std::string type; +}; + +struct JSONTraceSessionBase { + JSONTraceSessionBase() = default; + + JSONTraceSessionBase(std::vector processes) + : processes(processes) {} + std::vector processes; +}; + +/// The trace plug-in implementation should provide its own TPluginSettings, +/// which corresponds to the "trace" section of the schema. +template +struct JSONTraceSession : JSONTraceSessionBase { + TPluginSettings trace; +}; + +} // namespace lldb_private + +namespace llvm { +namespace json { + +llvm::json::Value toJSON(const lldb_private::JSONModule &module); + +llvm::json::Value toJSON(const lldb_private::JSONThread &thread); + +llvm::json::Value toJSON(const lldb_private::JSONProcess &process); + +llvm::json::Value +toJSON(const lldb_private::JSONTraceSessionBase &session_base); + +bool fromJSON(const Value &value, lldb_private::JSONAddress &address, + Path path); + +bool fromJSON(const Value &value, lldb_private::JSONModule &module, Path path); + +bool fromJSON(const Value &value, lldb_private::JSONThread &thread, Path path); + +bool fromJSON(const Value &value, lldb_private::JSONProcess &process, + Path path); + +bool fromJSON(const Value &value, + lldb_private::JSONTracePluginSettings &plugin_settings, + Path path); + +bool fromJSON(const Value &value, lldb_private::JSONTraceSessionBase &session, + Path path); + +template +bool fromJSON(const Value &value, + lldb_private::JSONTraceSession &session, + Path path) { + ObjectMapper o(value, path); + return o && o.map("trace", session.trace) && + fromJSON(value, (lldb_private::JSONTraceSessionBase &)session, path); +} + +} // namespace json +} // namespace llvm + +#endif // LLDB_TARGET_TRACEJSONSTRUCTS_H Index: lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp @@ -0,0 +1,107 @@ +//===-- TraceSessionFileStructs.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 "TraceJSONStructs.h" +#include "ThreadPostMortemTrace.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include + +using namespace lldb_private; +namespace llvm { +namespace json { + +llvm::json::Value toJSON(const JSONModule &module) { + llvm::json::Object result; + result["systemPath"] = module.system_path; + if (module.file) + result["file"] = *module.file; + std::ostringstream oss; + oss << "0x" << std::hex << module.load_address.value; + std::string str(oss.str()); + result["loadAddress"] = str; + if (module.uuid) + result["uuid"] = *module.uuid; + return std::move(result); +} + +llvm::json::Value toJSON(const JSONThread &thread) { + return Value(Object{{"tid", thread.tid}, {"traceFile", thread.trace_file}}); +} + +llvm::json::Value toJSON(const JSONProcess &process) { + llvm::json::Object result; + result["pid"] = process.pid; + result["triple"] = process.triple; + + llvm::json::Array threads_arr; + for (JSONThread e : process.threads) { + threads_arr.push_back(toJSON(e)); + } + result["threads"] = llvm::json::Value(std::move(threads_arr)); + + llvm::json::Array modules_arr; + for (JSONModule e : process.modules) { + modules_arr.push_back(toJSON(e)); + } + result["modules"] = llvm::json::Value(std::move(modules_arr)); + + return std::move(result); +} + +llvm::json::Value toJSON(const JSONTraceSessionBase &session_base) { + llvm::json::Array arr; + for (JSONProcess e : session_base.processes) { + arr.push_back(toJSON(e)); + } + return std::move(arr); +} + +bool fromJSON(const Value &value, JSONAddress &address, Path path) { + Optional s = value.getAsString(); + if (s.hasValue() && !s->getAsInteger(0, address.value)) + return true; + + path.report("expected numeric string"); + return false; +} + +bool fromJSON(const Value &value, JSONModule &module, Path path) { + ObjectMapper o(value, path); + return o && o.map("systemPath", module.system_path) && + o.map("file", module.file) && + o.map("loadAddress", module.load_address) && + o.map("uuid", module.uuid); +} + +bool fromJSON(const Value &value, JSONThread &thread, Path path) { + ObjectMapper o(value, path); + return o && o.map("tid", thread.tid) && o.map("traceFile", thread.trace_file); +} + +bool fromJSON(const Value &value, JSONProcess &process, Path path) { + ObjectMapper o(value, path); + return o && o.map("pid", process.pid) && o.map("triple", process.triple) && + o.map("threads", process.threads) && o.map("modules", process.modules); +} + +bool fromJSON(const Value &value, JSONTracePluginSettings &plugin_settings, + Path path) { + ObjectMapper o(value, path); + return o && o.map("type", plugin_settings.type); +} + +bool fromJSON(const Value &value, JSONTraceSessionBase &session, Path path) { + ObjectMapper o(value, path); + return o && o.map("processes", session.processes); +} + +} // namespace json +} // namespace llvm Index: lldb/source/Plugins/Trace/common/TraceSessionFileParser.h =================================================================== --- lldb/source/Plugins/Trace/common/TraceSessionFileParser.h +++ lldb/source/Plugins/Trace/common/TraceSessionFileParser.h @@ -9,9 +9,8 @@ #ifndef LLDB_TARGET_TRACESESSIONPARSER_H #define LLDB_TARGET_TRACESESSIONPARSER_H -#include "llvm/Support/JSON.h" - #include "ThreadPostMortemTrace.h" +#include "TraceJSONStructs.h" namespace lldb_private { @@ -24,46 +23,6 @@ /// See \a Trace::FindPlugin for more information regarding these JSON files. class TraceSessionFileParser { public: - /// C++ structs representing the JSON trace session. - /// \{ - struct JSONAddress { - lldb::addr_t value; - }; - - struct JSONModule { - std::string system_path; - llvm::Optional file; - JSONAddress load_address; - llvm::Optional uuid; - }; - - struct JSONThread { - int64_t tid; - std::string trace_file; - }; - - struct JSONProcess { - int64_t pid; - std::string triple; - std::vector threads; - std::vector modules; - }; - - struct JSONTracePluginSettings { - std::string type; - }; - - struct JSONTraceSessionBase { - std::vector processes; - }; - - /// The trace plug-in implementation should provide its own TPluginSettings, - /// which corresponds to the "trace" section of the schema. - template - struct JSONTraceSession : JSONTraceSessionBase { - TPluginSettings trace; - }; - /// \} /// Helper struct holding the objects created when parsing a process struct ParsedProcess { @@ -130,50 +89,5 @@ }; } // namespace lldb_private -namespace llvm { -namespace json { - -bool fromJSON(const Value &value, - lldb_private::TraceSessionFileParser::JSONAddress &address, - Path path); - -bool fromJSON(const Value &value, - lldb_private::TraceSessionFileParser::JSONModule &module, - Path path); - -bool fromJSON(const Value &value, - lldb_private::TraceSessionFileParser::JSONThread &thread, - Path path); - -bool fromJSON(const Value &value, - lldb_private::TraceSessionFileParser::JSONProcess &process, - Path path); - -bool fromJSON(const Value &value, - lldb_private::TraceSessionFileParser::JSONTracePluginSettings - &plugin_settings, - Path path); - -bool fromJSON( - const Value &value, - lldb_private::TraceSessionFileParser::JSONTraceSessionBase &session, - Path path); - -template -bool fromJSON( - const Value &value, - lldb_private::TraceSessionFileParser::JSONTraceSession - &session, - Path path) { - ObjectMapper o(value, path); - return o && o.map("trace", session.trace) && - fromJSON(value, - (lldb_private::TraceSessionFileParser::JSONTraceSessionBase &) - session, - path); -} - -} // namespace json -} // namespace llvm #endif // LLDB_TARGET_TRACESESSIONPARSER_H Index: lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp =================================================================== --- lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp +++ lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp @@ -170,55 +170,3 @@ } return parsed_processes; } - -namespace llvm { -namespace json { - -bool fromJSON(const Value &value, TraceSessionFileParser::JSONAddress &address, - Path path) { - Optional s = value.getAsString(); - if (s.hasValue() && !s->getAsInteger(0, address.value)) - return true; - - path.report("expected numeric string"); - return false; -} - -bool fromJSON(const Value &value, TraceSessionFileParser::JSONModule &module, - Path path) { - ObjectMapper o(value, path); - return o && o.map("systemPath", module.system_path) && - o.map("file", module.file) && - o.map("loadAddress", module.load_address) && - o.map("uuid", module.uuid); -} - -bool fromJSON(const Value &value, TraceSessionFileParser::JSONThread &thread, - Path path) { - ObjectMapper o(value, path); - return o && o.map("tid", thread.tid) && o.map("traceFile", thread.trace_file); -} - -bool fromJSON(const Value &value, TraceSessionFileParser::JSONProcess &process, - Path path) { - ObjectMapper o(value, path); - return o && o.map("pid", process.pid) && o.map("triple", process.triple) && - o.map("threads", process.threads) && o.map("modules", process.modules); -} - -bool fromJSON(const Value &value, - TraceSessionFileParser::JSONTracePluginSettings &plugin_settings, - Path path) { - ObjectMapper o(value, path); - return o && o.map("type", plugin_settings.type); -} - -bool fromJSON(const Value &value, - TraceSessionFileParser::JSONTraceSessionBase &session, - Path path) { - ObjectMapper o(value, path); - return o && o.map("processes", session.processes); -} - -} // namespace json -} // namespace llvm Index: lldb/source/Plugins/Trace/common/TraceSessionSaver.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/common/TraceSessionSaver.h @@ -0,0 +1,114 @@ +//===-- SessionSaver.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_TARGET_TRACESESSIONSAVER_H +#define LLDB_TARGET_TRACESESSIONSAVER_H + +#include "TraceJSONStructs.h" + +namespace lldb_private { + +class TraceSessionSaver { + +public: + /// Save the trace's session to a given directory as a JSON file. + /// The filename is \a /trace.json + /// + /// \param[in] trace_session_jo + /// The trace's session, as JSON Object. + /// + /// \param[in] directory + /// The directory where the JSON file will be saved. + /// + /// \return + /// \a llvm::success if the operation was successful, or an \a llvm::Error + /// otherwise. + /// + static llvm::Error + WriteSessionToFile(const llvm::json::Value &trace_session_jo, + FileSpec directory); + + /// Build processes section of the trace's session. + /// + /// \param[in] live_process + /// The process being traced. + /// + /// \param[in] raw_trace_fetcher + /// Callback function that receives a thread ID and returns its raw trace. + /// return raw trace in bytes if the operation was successful. + /// return an \a llvm::Error if failed at getting raw trace. + /// return \a None if raw trace got is INVALID. + /// + /// \param[in] directory + /// The directory where the thread files will be saved when building + /// processes section. + /// + /// \return The processes section to be built. Or an error if we can't build + /// processes section. + /// + static llvm::Expected BuildProcessesSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory); + + /// Build threads sub-section inside processes section of the trace's session. + /// The raw trace for a thread is written to a file with filename + /// \a /thread_id.trace. + /// + /// \param[in] live_process + /// The process being traced. + /// + /// \param[in] raw_trace_fetcher + /// Callback function that receives a thread ID and returns its raw trace. + /// return raw trace in bytes if the operation was successful. + /// return an \a llvm::Error if failed at getting raw trace. + /// return \a None if raw trace got is INVALID. + /// + /// \param[in] directory + /// The directory where the thread files will be saved when building + /// the threads section. + /// + /// \return The threads section to be built. Or an error if we can't build + /// threads sections. + /// + static llvm::Expected> BuildThreadsSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory); +}; + +/// Build modules sub-section inside processes section of the trace's session. +/// The original modules will be copied over to the \a +/// folder. Invalid modules are skipped. +/// Saving modules have the benifits of making trace saving more +/// self-contained, as both trace and modules can now be saved on one machine, +/// then sent over and loaded on another machine. +/// +/// \param[in] live_process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where the modules files will be saved when building +/// the modules section. +/// Example: If a module \a libbar.so exists in the path +/// \a /usr/lib/foo/libbar.so, then it will be copied to +/// \a /modules/usr/lib/foo/libbar.so. +/// +/// \return The modules section to be built. Or an error if we can't build +/// modules section. +/// +static llvm::Expected> +BuildModulesSection(Process &live_process, FileSpec directory); + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACESESSIONSAVER_H Index: lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp @@ -0,0 +1,157 @@ +//===-- TraceSessionSaver.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 "TraceSessionSaver.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" + +#include + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +llvm::Error +TraceSessionSaver::WriteSessionToFile(const llvm::json::Value &trace_session_jo, + FileSpec directory) { + + std::string out = formatv("{0:2}", trace_session_jo); + FileSpec trace_path = directory; + trace_path.AppendPathComponent("trace.json"); + std::ofstream os(trace_path.GetPath()); + os << out; + os.close(); + if (!os) + return createStringError(inconvertibleErrorCode(), + formatv("couldn't write to the file {0}", + trace_path.GetPath().c_str())); + return Error::success(); +} + +llvm::Expected TraceSessionSaver::BuildProcessesSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory) { + + JSONTraceSessionBase result; + Expected> json_threads = + BuildThreadsSection(live_process, raw_trace_fetcher, directory); + if (!json_threads) + return json_threads.takeError(); + + Expected> json_modules = + BuildModulesSection(live_process, directory); + if (!json_modules) { + return json_modules.takeError(); + } + + JSONProcess json_process( + live_process.GetID(), + live_process.GetTarget().GetArchitecture().GetTriple().getTriple(), + json_threads.get(), json_modules.get()); + + result.processes.push_back(json_process); + return result; +} + +llvm::Expected> TraceSessionSaver::BuildThreadsSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory) { + std::vector result; + for (ThreadSP thread_sp : live_process.Threads()) { + JSONThread json_thread; + json_thread.tid = thread_sp->GetID(); + // resolve the directory just in case + FileSystem::Instance().Resolve(directory); + FileSpec raw_trace_path = directory; + raw_trace_path.AppendPathComponent(std::to_string(thread_sp->GetID()) + + ".trace"); + json_thread.trace_file = raw_trace_path.GetPath().c_str(); + result.push_back(json_thread); + + llvm::Expected>> raw_trace = + raw_trace_fetcher(thread_sp->GetID()); + + if (!raw_trace) + return raw_trace.takeError(); + if (!raw_trace.get()) { + continue; + } + std::basic_fstream raw_trace_fs = + std::fstream(json_thread.trace_file, std::ios::out | std::ios::binary); + raw_trace_fs.write(reinterpret_cast(&raw_trace.get()->at(0)), + raw_trace.get()->size() * sizeof(uint8_t)); + raw_trace_fs.close(); + if (!raw_trace_fs) { + return createStringError( + inconvertibleErrorCode(), + formatv("couldn't write to the file {0}", json_thread.trace_file)); + } + } + return result; +} + +llvm::Expected> +TraceSessionSaver::BuildModulesSection(Process &live_process, + FileSpec directory) { + std::vector result; + ModuleList module_list = live_process.GetTarget().GetImages(); + for (size_t i = 0; i < module_list.GetSize(); ++i) { + ModuleSP module_sp(module_list.GetModuleAtIndex(i)); + if (!module_sp) + continue; + std::string system_path = module_sp->GetPlatformFileSpec().GetPath(); + // TODO: support memory-only libraries like [vdso] + if (!module_sp->GetFileSpec().IsAbsolute()) { + continue; + } + std::string file = module_sp->GetFileSpec().GetPath(); + ObjectFile *objfile = module_sp->GetObjectFile(); + if (objfile == nullptr) { + continue; + } + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + Address base_addr(objfile->GetBaseAddress()); + if (base_addr.IsValid() && + !live_process.GetTarget().GetSectionLoadList().IsEmpty()) { + load_addr = base_addr.GetLoadAddress(&live_process.GetTarget()); + } + if (load_addr == LLDB_INVALID_ADDRESS) + continue; + + FileSystem::Instance().Resolve(directory); + FileSpec path_to_copy_module = directory; + path_to_copy_module.AppendPathComponent("modules"); + path_to_copy_module.AppendPathComponent(system_path); + sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString()); + + if (std::error_code ec = llvm::sys::fs::copy_file( + system_path, path_to_copy_module.GetPath())) + return createStringError( + inconvertibleErrorCode(), + formatv("couldn't write to the file. {0}", ec.message())); + + JSONAddress load_address(load_addr); + JSONModule json_module(system_path, path_to_copy_module.GetPath(), + load_address, module_sp->GetUUID().GetAsString()); + result.push_back(json_module); + } + return result; +} Index: lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -19,7 +19,9 @@ IntelPTDecoder.cpp TraceCursorIntelPT.cpp TraceIntelPT.cpp + TraceIntelPTJSONStructs.cpp TraceIntelPTSessionFileParser.cpp + TraceIntelPTSessionSaver.cpp LINK_LIBS lldbCore Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -11,6 +11,9 @@ #include "IntelPTDecoder.h" #include "TraceIntelPTSessionFileParser.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/raw_ostream.h" namespace lldb_private { namespace trace_intel_pt { @@ -19,6 +22,8 @@ public: void Dump(Stream *s) const override; + llvm::Error SaveLiveTraceToDisk(FileSpec directory) override; + ~TraceIntelPT() override = default; /// PluginInterface protocol @@ -74,7 +79,7 @@ void DoRefreshLiveProcessState( llvm::Expected state) override; - bool IsTraced(const Thread &thread) override; + bool IsTraced(lldb::tid_t tid) override; const char *GetStartConfigurationHelp() override; @@ -170,7 +175,7 @@ /// It is provided by either a session file or a live process' "cpuInfo" /// binary data. llvm::Optional m_cpu_info; - std::map> m_thread_decoders; + std::map> m_thread_decoders; /// Error gotten after a failed live process update, if any. llvm::Optional m_live_refresh_error; }; Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -13,9 +13,11 @@ #include "DecodedThread.h" #include "TraceIntelPTConstants.h" #include "TraceIntelPTSessionFileParser.h" +#include "TraceIntelPTSessionSaver.h" #include "lldb/Core/PluginManager.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" +#include "llvm/ADT/None.h" using namespace lldb; using namespace lldb_private; @@ -66,6 +68,16 @@ void TraceIntelPT::Dump(Stream *s) const {} +llvm::Error TraceIntelPT::SaveLiveTraceToDisk(FileSpec directory) { + RefreshLiveProcessState(); + if (!m_live_process) + return createStringError(inconvertibleErrorCode(), + "Saving live trace requires a live process."); + + return TraceIntelPTSessionSaver().SaveToDisk(*m_live_process, *this, + directory); +} + Expected TraceIntelPT::CreateInstanceForSessionFile( const json::Value &trace_session_file, StringRef session_file_dir, Debugger &debugger) { @@ -86,7 +98,8 @@ : m_cpu_info(cpu_info) { for (const ThreadPostMortemTraceSP &thread : traced_threads) m_thread_decoders.emplace( - thread.get(), std::make_unique(thread, *this)); + thread->GetID(), + std::make_unique(thread, *this)); } DecodedThreadSP TraceIntelPT::Decode(Thread &thread) { @@ -96,7 +109,7 @@ thread.shared_from_this(), createStringError(inconvertibleErrorCode(), *m_live_refresh_error)); - auto it = m_thread_decoders.find(&thread); + auto it = m_thread_decoders.find(thread.GetID()); if (it == m_thread_decoders.end()) return std::make_shared( thread.shared_from_this(), @@ -120,7 +133,7 @@ } Optional TraceIntelPT::GetRawTraceSize(Thread &thread) { - if (IsTraced(thread)) + if (IsTraced(thread.GetID())) return Decode(thread)->GetRawTraceSize(); else return None; @@ -201,13 +214,13 @@ Thread &thread = *m_live_process->GetThreadList().FindThreadByID(thread_state.tid); m_thread_decoders.emplace( - &thread, std::make_unique(thread, *this)); + thread_state.tid, std::make_unique(thread, *this)); } } -bool TraceIntelPT::IsTraced(const Thread &thread) { +bool TraceIntelPT::IsTraced(lldb::tid_t tid) { RefreshLiveProcessState(); - return m_thread_decoders.count(&thread); + return m_thread_decoders.count(tid); } // The information here should match the description of the intel-pt section Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h @@ -0,0 +1,86 @@ +//===-- TraceIntelPTJSONStructs.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_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H + +#include "../common/TraceJSONStructs.h" +#include + +namespace lldb_private { +namespace trace_intel_pt { + +struct JSONTraceIntelPTCPUInfo { + JSONTraceIntelPTCPUInfo() = default; + + JSONTraceIntelPTCPUInfo(pt_cpu cpu_info) { + family = static_cast(cpu_info.family); + model = static_cast(cpu_info.model); + stepping = static_cast(cpu_info.stepping); + vendor = cpu_info.vendor == pcv_intel ? "intel" : "Unknown"; + } + + int64_t family; + int64_t model; + int64_t stepping; + std::string vendor; +}; + +struct JSONTraceIntelPTTrace { + JSONTraceIntelPTTrace() = default; + + JSONTraceIntelPTTrace(std::string type, JSONTraceIntelPTCPUInfo cpuInfo) + : type(type), cpuInfo(cpuInfo) {} + + std::string type; + JSONTraceIntelPTCPUInfo cpuInfo; +}; + +struct JSONTraceIntelPTSession { + JSONTraceIntelPTSession() = default; + + JSONTraceIntelPTSession(JSONTraceIntelPTTrace ipt_trace, + JSONTraceSessionBase session_base) + : ipt_trace(ipt_trace), session_base(session_base) {} + + JSONTraceIntelPTTrace ipt_trace; + JSONTraceSessionBase session_base; +}; + +struct JSONTraceIntelPTSettings : JSONTracePluginSettings { + JSONTraceIntelPTCPUInfo cpuInfo; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +namespace llvm { +namespace json { + +bool fromJSON( + const Value &value, + lldb_private::trace_intel_pt::JSONTraceIntelPTSettings &plugin_settings, + Path path); + +bool fromJSON(const llvm::json::Value &value, + lldb_private::trace_intel_pt::JSONTraceIntelPTCPUInfo &packet, + llvm::json::Path path); + +llvm::json::Value +toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTCPUInfo &cpu_info); + +llvm::json::Value +toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTTrace &trace); + +llvm::json::Value +toJSON(const lldb_private::trace_intel_pt::JSONTraceIntelPTSession &session); + +} // namespace json +} // namespace llvm + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp @@ -0,0 +1,59 @@ +//===-- TraceIntelPTJSONStructs.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 "TraceIntelPTJSONStructs.h" + +#include "llvm/Support/JSON.h" +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +namespace llvm { +namespace json { + +bool fromJSON(const Value &value, JSONTraceIntelPTSettings &plugin_settings, + Path path) { + ObjectMapper o(value, path); + return o && o.map("cpuInfo", plugin_settings.cpuInfo) && + fromJSON(value, (JSONTracePluginSettings &)plugin_settings, path); +} + +bool fromJSON(const json::Value &value, JSONTraceIntelPTCPUInfo &cpu_info, + Path path) { + ObjectMapper o(value, path); + return o && o.map("vendor", cpu_info.vendor) && + o.map("family", cpu_info.family) && o.map("model", cpu_info.model) && + o.map("stepping", cpu_info.stepping); +} + +Value toJSON(const JSONTraceIntelPTCPUInfo &cpu_info) { + return Value(Object{{"family", cpu_info.family}, + {"model", cpu_info.model}, + {"stepping", cpu_info.stepping}, + {"vendor", cpu_info.vendor}}); +} + +llvm::json::Value toJSON(const JSONTraceIntelPTTrace &trace) { + llvm::json::Object result; + result["type"] = trace.type; + result["cpuInfo"] = toJSON(trace.cpuInfo); + return std::move(result); +} + +llvm::json::Value toJSON(const JSONTraceIntelPTSession &session) { + llvm::json::Object result; + result["trace"] = toJSON(session.ipt_trace); + result["processes"] = toJSON(session.session_base); + return std::move(result); +} + +} // namespace json +} // namespace llvm Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -72,3 +72,13 @@ "packets as: 2 ^ (value + 11), e.g. value 3 means 16KiB between PSB " "packets. Defaults to 0 if supported.">; } + +let Command = "process trace save intel pt" in { + def process_trace_save_intel_directory: Option<"directory", "d">, + Group<1>, + Arg<"Value">, Required, + Desc<"This value defines the directory where the trace will be saved." + "It will be created if it does not exist. It will also create a " + "trace files with the trace data and a trace.json with the main " + "properties of the trace session.">; +} Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h @@ -9,9 +9,9 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H -#include "TraceIntelPT.h" - #include "../common/TraceSessionFileParser.h" +#include "TraceIntelPT.h" +#include "TraceIntelPTJSONStructs.h" namespace lldb_private { namespace trace_intel_pt { @@ -20,17 +20,6 @@ class TraceIntelPTSessionFileParser : public TraceSessionFileParser { public: - struct JSONTraceIntelPTCPUInfo { - int64_t family; - int64_t model; - int64_t stepping; - std::string vendor; - }; - - struct JSONTraceIntelPTSettings - : TraceSessionFileParser::JSONTracePluginSettings { - JSONTraceIntelPTCPUInfo cpuInfo; - }; /// See \a TraceSessionFileParser::TraceSessionFileParser for the description /// of these fields. @@ -65,24 +54,5 @@ } // namespace trace_intel_pt } // namespace lldb_private -namespace llvm { -namespace json { - -bool fromJSON(const Value &value, - lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: - JSONTraceIntelPTSettings &plugin_settings, - Path path); - -bool fromJSON(const llvm::json::Value &value, - lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: - JSONTraceIntelPTCPUInfo &packet, - llvm::json::Path path); - -llvm::json::Value -toJSON(const lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: - JSONTraceIntelPTCPUInfo &packet); - -} // namespace json -} // namespace llvm #endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -9,10 +9,7 @@ #include "TraceIntelPTSessionFileParser.h" #include "../common/ThreadPostMortemTrace.h" -#include "lldb/Core/Debugger.h" -#include "lldb/Target/Process.h" -#include "lldb/Target/Target.h" -#include "lldb/Target/ThreadList.h" +#include "TraceIntelPT.h" using namespace lldb; using namespace lldb_private; @@ -59,7 +56,7 @@ Expected TraceIntelPTSessionFileParser::Parse() { json::Path::Root root("traceSession"); - TraceSessionFileParser::JSONTraceSession session; + JSONTraceSession session; if (!json::fromJSON(m_trace_session_file, session, root)) return CreateJSONError(root, m_trace_session_file); @@ -70,38 +67,3 @@ else return parsed_processes.takeError(); } - -namespace llvm { -namespace json { - -bool fromJSON( - const Value &value, - TraceIntelPTSessionFileParser::JSONTraceIntelPTSettings &plugin_settings, - Path path) { - ObjectMapper o(value, path); - return o && o.map("cpuInfo", plugin_settings.cpuInfo) && - fromJSON( - value, - (TraceSessionFileParser::JSONTracePluginSettings &)plugin_settings, - path); -} - -bool fromJSON(const json::Value &value, - TraceIntelPTSessionFileParser::JSONTraceIntelPTCPUInfo &cpu_info, - Path path) { - ObjectMapper o(value, path); - return o && o.map("vendor", cpu_info.vendor) && - o.map("family", cpu_info.family) && o.map("model", cpu_info.model) && - o.map("stepping", cpu_info.stepping); -} - -Value toJSON( - const TraceIntelPTSessionFileParser::JSONTraceIntelPTCPUInfo &cpu_info) { - return Value(Object{{"family", cpu_info.family}, - {"model", cpu_info.model}, - {"stepping", cpu_info.stepping}, - {"vendor", cpu_info.vendor}}); -} - -} // namespace json -} // namespace llvm Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h @@ -0,0 +1,63 @@ +//===-- TraceIntelPTSessionSaver.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_TRACE_INTEL_PT_TRACEINTELPTSESSIONSAVER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONSAVER_H + +#include "TraceIntelPT.h" + +#include "../common/TraceJSONStructs.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT; + +class TraceIntelPTSessionSaver { + +public: + /// Save the Intel PT trace of a given live process to the specified + /// directory, which will be created if needed. This will also create a file + /// /trace.json with the main properties of the trace + /// session, along with others files which contain the actual trace data. + /// The trace.json file can be used later as input for the "trace load" + /// command to load the trace in LLDB. + /// + /// \param[in] live_process + /// The process whose trace will be saved to disk. + /// + /// \param[in] trace_ipt + /// The Intel PT trace to be saved to disk. + /// + /// \param[in] directory + /// The directory where the trace files will be saved. + /// + /// \return + /// \a llvm::success if the operation was successful, or an \a llvm::Error + /// otherwise. + /// + llvm::Error SaveToDisk(Process &live_process, TraceIntelPT &trace_ipt, + FileSpec directory); + +private: + /// Build trace section of the intel-pt tracing session. + /// + /// \param[in] trace_ipt + /// The Intel PT trace. + /// + /// \return The trace section to be built. Or an error if we can't build + /// trace section. + /// + llvm::Expected + BuildTraceSection(TraceIntelPT &trace_ipt); +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONSAVER_H Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp @@ -0,0 +1,78 @@ +//===-- TraceIntelPTSessionSaver.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 "TraceIntelPTSessionSaver.h" +#include "../common/TraceSessionSaver.h" +#include "TraceIntelPT.h" +#include "TraceIntelPTJSONStructs.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/lldb-types.h" +#include "llvm/ADT/None.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" + +#include +#include +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +llvm::Error TraceIntelPTSessionSaver::SaveToDisk(Process &live_process, + TraceIntelPT &trace_ipt, + FileSpec directory) { + if (std::error_code ec = + sys::fs::create_directories(directory.GetPath().c_str())) + return llvm::errorCodeToError(ec); + + llvm::Expected json_trace_intel_pt_trace = + BuildTraceSection(trace_ipt); + if (!json_trace_intel_pt_trace) + return json_trace_intel_pt_trace.takeError(); + + llvm::Expected json_trace_session_base = + TraceSessionSaver::BuildProcessesSection( + live_process, + [&](lldb::tid_t tid) + -> llvm::Expected>> { + if (!trace_ipt.IsTraced(tid)) + return None; + return trace_ipt.GetLiveThreadBuffer(tid); + }, + directory); + + if (!json_trace_session_base) + return json_trace_session_base.takeError(); + + JSONTraceIntelPTSession json_trace_intel_pt_session( + json_trace_intel_pt_trace.get(), json_trace_session_base.get()); + + llvm::json::Value intel_pt_session_jo = + llvm::json::toJSON(json_trace_intel_pt_session); + return TraceSessionSaver::WriteSessionToFile(intel_pt_session_jo, directory); +} + +llvm::Expected +TraceIntelPTSessionSaver::BuildTraceSection(TraceIntelPT &trace_ipt) { + llvm::Expected cpu_info = trace_ipt.GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); + + JSONTraceIntelPTCPUInfo json_trace_intel_pt_cpu_info(cpu_info.get()); + + JSONTraceIntelPTTrace result("intel-pt", json_trace_intel_pt_cpu_info); + return result; +} Index: lldb/test/API/commands/trace/TestTraceSave.py =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/TestTraceSave.py @@ -0,0 +1,75 @@ +import lldb +from intelpt_testcase import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceSave(TraceIntelPTTestCaseBase): + mydir = TestBase.compute_mydir(__file__) + + def testErrorMessages(self): + # We first check the output when there are no targets + self.expect("process trace save", + 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("process trace save", + 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("process trace save", + substrs=["error: Process is not being traced"], + error=True) + + def testSaveTrace(self): + self.expect("target create " + + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + self.expect("b main") + self.expect("r") + self.expect("thread trace start") + self.expect("n") + self.expect("n") + self.expect("n") + self.expect("n") + self.expect("n") + self.expect("n") + self.expect("n") + self.expect("n") + self.expect("n") + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + + ci.HandleCommand("thread trace dump instructions -c 10 --forwards", res) + self.assertEqual(res.Succeeded(), True) + first_ten_instructions = res.GetOutput() + + ci.HandleCommand("thread trace dump instructions -c 10", res) + self.assertEqual(res.Succeeded(), True) + last_ten_instructions = res.GetOutput() + + # Save the trace to + self.expect("process trace save -d " + + os.path.join(self.getBuildDir(), "intelpt-trace", "trace_copy_dir")) + + # Load the trace just saved + self.expect("trace load -v " + + os.path.join(self.getBuildDir(), "intelpt-trace", "trace_copy_dir", "trace.json"), + substrs=["intel-pt"]) + + # Compare with instructions saved at the first time + ci.HandleCommand("thread trace dump instructions -c 10 --forwards", res) + self.assertEqual(res.Succeeded(), True) + self.assertEqual(res.GetOutput(), first_ten_instructions) + + ci.HandleCommand("thread trace dump instructions -c 10", res) + self.assertEqual(res.Succeeded(), True) + self.assertEqual(res.GetOutput(), last_ten_instructions)