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 @@ -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,22 @@ /// 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 +173,12 @@ /// Check if a thread is currently traced by this object. /// - /// \param[in] thread - /// The thread in question. + /// \param[in] tid + /// The id of 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. diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -1644,6 +1644,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: @@ -1681,6 +1755,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( 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 @@ -744,6 +744,14 @@ "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. " diff --git a/lldb/source/Plugins/Trace/common/CMakeLists.txt b/lldb/source/Plugins/Trace/common/CMakeLists.txt --- a/lldb/source/Plugins/Trace/common/CMakeLists.txt +++ b/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 diff --git a/lldb/source/Plugins/Trace/common/TraceJSONStructs.h b/lldb/source/Plugins/Trace/common/TraceJSONStructs.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/common/TraceJSONStructs.h @@ -0,0 +1,98 @@ +//===-- 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 { + +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; +}; + +} // 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 diff --git a/lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp b/lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/common/TraceJSONStructs.cpp @@ -0,0 +1,106 @@ +//===-- 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 json_module; + json_module["systemPath"] = module.system_path; + if (module.file) + json_module["file"] = *module.file; + std::ostringstream oss; + oss << "0x" << std::hex << module.load_address.value; + json_module["loadAddress"] = oss.str(); + if (module.uuid) + json_module["uuid"] = *module.uuid; + return std::move(json_module); +} + +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 json_process; + json_process["pid"] = process.pid; + json_process["triple"] = process.triple; + + llvm::json::Array threads_arr; + for (JSONThread e : process.threads) + threads_arr.push_back(toJSON(e)); + + json_process["threads"] = llvm::json::Value(std::move(threads_arr)); + + llvm::json::Array modules_arr; + for (JSONModule e : process.modules) + modules_arr.push_back(toJSON(e)); + + json_process["modules"] = llvm::json::Value(std::move(modules_arr)); + + return std::move(json_process); +} + +llvm::json::Value toJSON(const JSONTraceSessionBase &session) { + llvm::json::Array arr; + for (JSONProcess e : session.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 diff --git a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.h b/lldb/source/Plugins/Trace/common/TraceSessionFileParser.h --- a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.h +++ b/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 diff --git a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp b/lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp --- a/lldb/source/Plugins/Trace/common/TraceSessionFileParser.cpp +++ b/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 diff --git a/lldb/source/Plugins/Trace/common/TraceSessionSaver.h b/lldb/source/Plugins/Trace/common/TraceSessionSaver.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/common/TraceSessionSaver.h @@ -0,0 +1,112 @@ +//===-- 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 session description JSON object inside the given directory + /// as a file named \a trace.json. + /// + /// \param[in] trace_session_description + /// 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_description, + FileSpec directory); + + /// Build the processes section of the trace session description file. Besides + /// returning the processes information, this method saves to disk all modules + /// and raw traces corresponding to the traced threads of the given process. + /// + /// \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. + /// This callback should return \a None if the thread is not being traced. + /// Otherwise, it should return the raw trace in bytes or an + /// \a llvm::Error in case of failures. + /// + /// \param[in] directory + /// The directory where files will be saved when building the processes + /// section. + /// + /// \return + /// The processes section or \a llvm::Error in case of failures. + static llvm::Expected BuildProcessesSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory); + + /// Build the threads sub-section of the trace session description file. + /// For each traced thread, its raw trace is also written to the file + /// \a thread_id_.trace inside of the given directory. + /// + /// \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. + /// This callback should return \a None if the thread is not being traced. + /// Otherwise, it should return the raw trace in bytes or an + /// \a llvm::Error in case of failures. + /// + /// \param[in] directory + /// The directory where files will be saved when building the threads + /// section. + /// + /// \return + /// The threads section or \a llvm::Error in case of failures. + static llvm::Expected> BuildThreadsSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory); + + /// Build modules sub-section of the trace's session. The original modules + /// will be copied over to the \a folder. Invalid modules + /// are skipped. + /// Copying the modules has the benefit of making these trace session + /// directories self-contained, as the raw traces and modules are part of the + /// output directory and can be sent to another machine, where lldb can load + /// them and replicate exactly the same trace session. + /// + /// \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 or \a llvm::Error in case of failures. + static llvm::Expected> + BuildModulesSection(Process &live_process, FileSpec directory); +}; +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACESESSIONSAVER_H diff --git a/lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp b/lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/common/TraceSessionSaver.cpp @@ -0,0 +1,149 @@ +//===-- 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_description, FileSpec directory) { + + FileSpec trace_path = directory; + trace_path.AppendPathComponent("trace.json"); + std::ofstream os(trace_path.GetPath()); + os << std::string(formatv("{0:2}", trace_session_description)); + 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 json_session_description; + 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(); + + json_session_description.processes.push_back(JSONProcess{ + static_cast(live_process.GetID()), + live_process.GetTarget().GetArchitecture().GetTriple().getTriple(), + json_threads.get(), json_modules.get()}); + return json_session_description; +} + +llvm::Expected> TraceSessionSaver::BuildThreadsSection( + Process &live_process, + std::function< + llvm::Expected>>(lldb::tid_t tid)> + raw_trace_fetcher, + FileSpec directory) { + std::vector json_threads; + for (ThreadSP thread_sp : live_process.Threads()) { + // 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_threads.push_back(JSONThread{static_cast(thread_sp->GetID()), + raw_trace_path.GetPath().c_str()}); + + 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( + raw_trace_path.GetPath().c_str(), 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}", + raw_trace_path.GetPath().c_str())); + } + } + return json_threads; +} + +llvm::Expected> +TraceSessionSaver::BuildModulesSection(Process &live_process, + FileSpec directory) { + std::vector json_modules; + 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())); + + json_modules.push_back( + JSONModule{system_path, path_to_copy_module.GetPath(), + JSONAddress{load_addr}, module_sp->GetUUID().GetAsString()}); + } + return json_modules; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt --- a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ b/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 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 @@ -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; @@ -139,6 +144,13 @@ llvm::Expected GetCPUInfo(); + /// Get the current traced live process. + /// + /// \return + /// The current traced live process. If it's not a live process, + /// return \a nullptr. + Process *GetLiveProcess(); + private: friend class TraceIntelPTSessionFileParser; @@ -170,7 +182,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; }; 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 @@ -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,11 @@ void TraceIntelPT::Dump(Stream *s) const {} +llvm::Error TraceIntelPT::SaveLiveTraceToDisk(FileSpec directory) { + RefreshLiveProcessState(); + return TraceIntelPTSessionSaver().SaveToDisk(*this, directory); +} + Expected TraceIntelPT::CreateInstanceForSessionFile( const json::Value &trace_session_file, StringRef session_file_dir, Debugger &debugger) { @@ -86,7 +93,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 +104,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 +128,7 @@ } Optional TraceIntelPT::GetRawTraceSize(Thread &thread) { - if (IsTraced(thread)) + if (IsTraced(thread.GetID())) return Decode(thread)->GetRawTraceSize(); else return None; @@ -188,6 +196,8 @@ return *m_cpu_info; } +Process *TraceIntelPT::GetLiveProcess() { return m_live_process; } + void TraceIntelPT::DoRefreshLiveProcessState( Expected state) { m_thread_decoders.clear(); @@ -201,13 +211,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 diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h @@ -0,0 +1,75 @@ +//===-- 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 { + std::string type; + JSONTraceIntelPTCPUInfo cpuInfo; +}; + +struct JSONTraceIntelPTSession { + 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 diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp new file mode 100644 --- /dev/null +++ b/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 json_trace; + json_trace["type"] = trace.type; + json_trace["cpuInfo"] = toJSON(trace.cpuInfo); + return std::move(json_trace); +} + +llvm::json::Value toJSON(const JSONTraceIntelPTSession &session) { + llvm::json::Object json_session; + json_session["trace"] = toJSON(session.ipt_trace); + json_session["processes"] = toJSON(session.session_base); + return std::move(json_session); +} + +} // namespace json +} // namespace llvm diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ b/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.">; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ b/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 diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ b/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 diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.h @@ -0,0 +1,57 @@ +//===-- 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 live process to the specified directory, + /// which will be created if needed. This will also create 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. + /// + /// \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(TraceIntelPT &trace_ipt, FileSpec directory); + +private: + /// Build trace section of the intel-pt trace session description file. + /// + /// \param[in] trace_ipt + /// The Intel PT trace. + /// + /// \return + /// The trace section an \a llvm::Error in case of failures. + llvm::Expected + BuildTraceSection(TraceIntelPT &trace_ipt); +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONSAVER_H diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp @@ -0,0 +1,79 @@ +//===-- 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(TraceIntelPT &trace_ipt, + FileSpec directory) { + Process *live_process = trace_ipt.GetLiveProcess(); + if (live_process == nullptr) + return createStringError(inconvertibleErrorCode(), + "Saving a trace requires a live process."); + + if (std::error_code ec = + sys::fs::create_directories(directory.GetPath().c_str())) + return llvm::errorCodeToError(ec); + + llvm::Expected json_intel_pt_trace = + BuildTraceSection(trace_ipt); + if (!json_intel_pt_trace) + return json_intel_pt_trace.takeError(); + + llvm::Expected json_session_description = + 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_session_description) + return json_session_description.takeError(); + + JSONTraceIntelPTSession json_intel_pt_session{json_intel_pt_trace.get(), + json_session_description.get()}; + + return TraceSessionSaver::WriteSessionToFile( + llvm::json::toJSON(json_intel_pt_session), directory); +} + +llvm::Expected +TraceIntelPTSessionSaver::BuildTraceSection(TraceIntelPT &trace_ipt) { + llvm::Expected cpu_info = trace_ipt.GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); + + return JSONTraceIntelPTTrace{"intel-pt", + JSONTraceIntelPTCPUInfo(cpu_info.get())}; +} diff --git a/lldb/test/API/commands/trace/TestTraceSave.py b/lldb/test/API/commands/trace/TestTraceSave.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceSave.py @@ -0,0 +1,97 @@ +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 testSaveToInvalidDir(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") + + # Check the output when saving without providing the directory argument + self.expect("process trace save -d", + substrs=["error: last option requires an argument"], + error=True) + + # Check the output when saving to an invalid directory + self.expect("process trace save -d /", + substrs=["error: couldn't write to the file"], + error=True) + + def testSaveWhenNotLiveTrace(self): + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + substrs=["intel-pt"]) + + # Check the output when not doing live tracing + self.expect("process trace save -d " + + os.path.join(self.getBuildDir(), "intelpt-trace", "trace_not_live_dir"), + substrs=["error: Saving a trace requires a live process."], + 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("b 7") + + 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() + + # Now, 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)