Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ lldb/include/lldb/Target/Trace.h @@ -55,6 +55,25 @@ /// A stream object to dump the information to. virtual void Dump(Stream *s) const = 0; + /// Save the trace or the given 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] directory + /// The directory where the trace files will be saved. + /// + /// \param[in] process_sp + /// The process whose trace will be saved to disk. + /// + /// \return + /// \a llvm::success if the operation was successful, or an \a llvm::Error + /// otherwise. + /// + virtual llvm::Error SaveToDisk(FileSpec directory, + lldb::ProcessSP &process_sp) = 0; + /// Find a trace plug-in using JSON data. /// /// When loading trace data from disk, the information for the trace data Index: lldb/source/Commands/CommandObjectProcess.cpp =================================================================== --- lldb/source/Commands/CommandObjectProcess.cpp +++ lldb/source/Commands/CommandObjectProcess.cpp @@ -1641,6 +1641,81 @@ } }; +// 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, " + "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", + "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->SaveToDisk(m_options.m_directory, process_sp)) + result.AppendError(toString(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); + } + + CommandOptions m_options; +}; + // CommandObjectProcessTraceStop class CommandObjectProcessTraceStop : public CommandObjectParsed { public: @@ -1678,6 +1753,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,5 +1,6 @@ add_lldb_library(lldbPluginTraceCommon ThreadPostMortemTrace.cpp + TraceJSONStructs.cpp TraceSessionFileParser.cpp LINK_LIBS Index: lldb/source/Plugins/Trace/common/TraceJSONStructs.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/common/TraceJSONStructs.h @@ -0,0 +1,127 @@ +//===-- 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/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 + TraceIntelPTSessionFileSaver.cpp LINK_LIBS lldbCore Index: lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h +++ lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -33,6 +33,12 @@ /// A \a DecodedThread instance. DecodedThreadSP Decode(); + /// Get thr raw trace of the thread. + /// + /// \return + /// raw trace from the thread buffer as bytes. + virtual llvm::Expected> GetRawTrace() = 0; + ThreadDecoder(const ThreadDecoder &other) = delete; ThreadDecoder &operator=(const ThreadDecoder &other) = delete; @@ -61,6 +67,8 @@ private: DecodedThreadSP DoDecode() override; + llvm::Expected> GetRawTrace() override; + lldb::ThreadPostMortemTraceSP m_trace_thread; TraceIntelPT &m_trace; }; @@ -77,6 +85,8 @@ private: DecodedThreadSP DoDecode() override; + llvm::Expected> GetRawTrace() override; + lldb::ThreadSP m_thread_sp; TraceIntelPT &m_trace; }; Index: lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -7,6 +7,7 @@ #include "IntelPTDecoder.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/MemoryBuffer.h" #include "../common/ThreadPostMortemTrace.h" @@ -263,6 +264,21 @@ return std::make_shared(m_trace_thread->shared_from_this(), instructions.takeError()); } +llvm::Expected> PostMortemThreadDecoder::GetRawTrace() { + FileSpec trace_file = m_trace_thread->GetTraceFile(); + ErrorOr> trace_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = trace_or_error.getError()) + return errorCodeToError(err); + MemoryBuffer &trace = **trace_or_error; + MutableArrayRef trace_data( + reinterpret_cast(const_cast(trace.getBufferStart())), + trace.getBufferSize()); + std::vector result; + for (size_t i = 0; i < trace_data.size(); i++) + result.push_back(trace_data[i]); + return result; +} LiveThreadDecoder::LiveThreadDecoder(Thread &thread, TraceIntelPT &trace) : m_thread_sp(thread.shared_from_this()), m_trace(trace) {} @@ -277,3 +293,7 @@ return std::make_shared(m_thread_sp, instructions.takeError()); } + +llvm::Expected> LiveThreadDecoder::GetRawTrace() { + return m_trace.GetLiveThreadBuffer(m_thread_sp->GetID()); +} 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,8 @@ #include "IntelPTDecoder.h" #include "TraceIntelPTSessionFileParser.h" +#include "lldb/Utility/FileSpec.h" +#include "llvm/Support/raw_ostream.h" namespace lldb_private { namespace trace_intel_pt { @@ -19,6 +21,11 @@ public: void Dump(Stream *s) const override; + llvm::Error SaveToDisk(FileSpec directory, + lldb::ProcessSP &process_sp) override; + + llvm::Expected> GetThreadBuffer(Thread &thread); + ~TraceIntelPT() override = default; /// PluginInterface protocol 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 "TraceIntelPTSessionFileSaver.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,26 @@ void TraceIntelPT::Dump(Stream *s) const {} +llvm::Error TraceIntelPT::SaveToDisk(FileSpec directory, + lldb::ProcessSP &process_sp) { + RefreshLiveProcessState(); + return TraceIntelPTSessionFileSaver().SaveToDisk(process_sp, *this, + directory); +} + +llvm::Expected> +TraceIntelPT::GetThreadBuffer(Thread &thread) { + auto it = m_thread_decoders.find(&thread); + if (it == m_thread_decoders.end()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "thread not traced"); + + if (!it->second->GetRawTrace()) + return it->second->GetRawTrace().takeError(); + + return it->second->GetRawTrace(); +} + Expected TraceIntelPT::CreateInstanceForSessionFile( const json::Value &trace_session_file, StringRef session_file_dir, Debugger &debugger) { Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h @@ -0,0 +1,82 @@ +//===-- 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" + +namespace lldb_private { +namespace trace_intel_pt { + +struct JSONTraceIntelPTCPUInfo { + JSONTraceIntelPTCPUInfo() = default; + + JSONTraceIntelPTCPUInfo(int64_t family, int64_t model, int64_t stepping, + std::string vendor) + : family(family), model(model), stepping(stepping), vendor(vendor) {} + + 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 JSONTraceIntelPTSchema { + JSONTraceIntelPTSchema() = default; + + JSONTraceIntelPTSchema(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::JSONTraceIntelPTSchema &schema); + +} // 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 JSONTraceIntelPTSchema &schema) { + llvm::json::Object result; + result["trace"] = toJSON(schema.ipt_trace); + result["processes"] = toJSON(schema.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/TraceIntelPTSessionFileSaver.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileSaver.h @@ -0,0 +1,124 @@ +//===-- TraceIntelPTSessionFileSaver.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_TRACEINTELPTSESSIONFILESAVER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILESAVER_H + +#include "TraceIntelPT.h" + +#include "../common/TraceSessionFileParser.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT; + +class TraceIntelPTSessionFileSaver { + +public: + /// Save the trace of the given 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] process_sp + /// 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(lldb::ProcessSP &process_sp, TraceIntelPT &trace_ipt, + FileSpec directory); + +private: + /// Save the intel-pt trace's schema with main properties of the trace + /// session to the specified directory as a JSON file. The filename is + /// \a /trace.json + /// + /// \param[in] json_trace_intel_pt_schema + /// schema that describes trace. + /// + /// \param[in] directory + /// The directory where the JSON file will be saved. + /// + void WriteIntelPTSchemaToFile( + const JSONTraceIntelPTSchema &json_trace_intel_pt_schema, + FileSpec directory); + + /// Build trace section of the intel-pt trace's schema. + /// + /// \param[in] trace_ipt + /// The Intel PT trace. + /// + /// \return The trace section to be built. + /// + llvm::Expected + BuildTraceSection(TraceIntelPT &trace_ipt); + + /// Build processes section of the intel-pt trace's schema. + /// + /// \param[in] process_sp + /// The process being traced. + /// + /// \param[in] trace_ipt + /// The Intel PT trace. + /// + /// \param[in] directory + /// The directory where the thread files will be saved when building + /// processes section. + /// + /// \return The processes section to be built. + /// + llvm::Expected + BuildProcessesSection(lldb::ProcessSP &process_sp, TraceIntelPT &trace_ipt, + FileSpec directory); + + /// Build threads sub-section inside processes section of the intel-pt trace's + /// schema. The raw trace for a thread is written to a file with filename + /// \a /thread_id.trace. + /// + /// \param[in] process_sp + /// The process being traced. + /// + /// \param[in] trace_ipt + /// The Intel PT trace. + /// + /// \param[in] directory + /// The directory where the thread files will be saved when building + /// the threads section. + /// + /// \return The threads section to be built. + /// + llvm::Expected> + BuildThreadsSection(lldb::ProcessSP &process_sp, TraceIntelPT &trace_ipt, + FileSpec directory); + + /// Build modules sub-section inside processes section of the intel-pt trace's + /// schema. Invalid modules are skipped. + /// + /// \param[in] process_sp + /// The process being traced. + /// + /// \return The modules section to be built. + // + std::vector BuildModulesSection(lldb::ProcessSP &process_sp); +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILESAVER_H Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileSaver.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileSaver.cpp @@ -0,0 +1,170 @@ +//===-- TraceIntelPTSessionFileSaver.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 "TraceIntelPTSessionFileSaver.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/Analysis/ModuleSummaryAnalysis.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" +#include +#include +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +llvm::Error TraceIntelPTSessionFileSaver::SaveToDisk( + lldb::ProcessSP &process_sp, 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 = + BuildProcessesSection(process_sp, trace_ipt, directory); + if (!json_trace_session_base) { + return json_trace_session_base.takeError(); + } + + struct JSONTraceIntelPTSchema json_trace_intel_pt_schema( + json_trace_intel_pt_trace.get(), json_trace_session_base.get()); + WriteIntelPTSchemaToFile(json_trace_intel_pt_schema, directory); + return llvm::Error::success(); +} + +void TraceIntelPTSessionFileSaver::WriteIntelPTSchemaToFile( + const JSONTraceIntelPTSchema &json_trace_intel_pt_schema, + FileSpec directory) { + llvm::json::Value ipt_schema_jo = + llvm::json::toJSON(json_trace_intel_pt_schema); + std::string out = formatv("{0:2}", ipt_schema_jo); + FileSpec trace_path = directory; + trace_path.AppendPathComponent("trace.json"); + std::ofstream os(trace_path.GetPath()); + os << out; + return; +} + +llvm::Expected +TraceIntelPTSessionFileSaver::BuildTraceSection(TraceIntelPT &trace_ipt) { + llvm::Expected cpu_info = trace_ipt.GetCPUInfo(); + if (!cpu_info) { + return cpu_info.takeError(); + } + + struct JSONTraceIntelPTCPUInfo json_trace_intel_pt_cpu_info( + static_cast(cpu_info->family), + static_cast(cpu_info->model), + static_cast(cpu_info->stepping), + cpu_info->vendor == pcv_intel ? "intel" : "Unknown"); + + struct JSONTraceIntelPTTrace result("intel-pt", json_trace_intel_pt_cpu_info); + return result; +} + +llvm::Expected +TraceIntelPTSessionFileSaver::BuildProcessesSection(lldb::ProcessSP &process_sp, + TraceIntelPT &trace_ipt, + FileSpec directory) { + JSONTraceSessionBase result; + Expected> json_threads = + BuildThreadsSection(process_sp, trace_ipt, directory); + if (!json_threads) { + return json_threads.takeError(); + } + + std::vector json_modules = BuildModulesSection(process_sp); + + struct JSONProcess json_process( + process_sp->GetID(), + process_sp->GetTarget().GetArchitecture().GetTriple().getTriple(), + json_threads.get(), json_modules); + result.processes.push_back(json_process); + + return result; +} + +llvm::Expected> +TraceIntelPTSessionFileSaver::BuildThreadsSection(lldb::ProcessSP &process_sp, + TraceIntelPT &trace_ipt, + FileSpec directory) { + std::vector result; + for (ThreadSP thread_sp : process_sp->Threads()) { + JSONThread json_thread; + json_thread.tid = thread_sp->GetID(); + // resolve the directory just in case + FileSystem::Instance().Resolve(directory); + FileSpec json_path = directory; + json_path.AppendPathComponent(std::to_string(thread_sp->GetID()) + + ".trace"); + json_thread.trace_file = json_path.GetPath().c_str(); + result.push_back(json_thread); + + Expected> raw_trace = + trace_ipt.GetThreadBuffer(*thread_sp); + if (!raw_trace) { + return raw_trace.takeError(); + } + std::basic_fstream raw_trace_file = + std::fstream(json_thread.trace_file, std::ios::out | std::ios::binary); + raw_trace_file.write(reinterpret_cast(&raw_trace->at(0)), + raw_trace->size() * sizeof(uint8_t)); + } + return result; +} + +std::vector +TraceIntelPTSessionFileSaver::BuildModulesSection(lldb::ProcessSP &process_sp) { + std::vector result; + ModuleList module_list = process_sp->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() && + !process_sp->GetTarget().GetSectionLoadList().IsEmpty()) { + load_addr = base_addr.GetLoadAddress(&process_sp->GetTarget()); + } + if (load_addr == LLDB_INVALID_ADDRESS) { + continue; + } + JSONAddress load_address(load_addr); + JSONModule json_module(system_path, file, load_address, + module_sp->GetUUID().GetAsString()); + result.push_back(json_module); + } + return result; +} Index: lldb/test/API/commands/trace/TestTraceSave.py =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/TestTraceSave.py @@ -0,0 +1,70 @@ +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): + # Load a trace + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + substrs=["intel-pt"]) + # Save the trace to + self.expect("process trace save -d " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_copy_dir")) + # Load the trace just saved + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_copy_dir", "trace.json"), + substrs=["intel-pt"]) + #dump instructions and check + self.expect("thread trace dump instructions --raw --count 21 --forwards", + substrs=['''thread #1: tid = 3842849 + [ 0] 0x0000000000400511 + [ 1] 0x0000000000400518 + [ 2] 0x000000000040051f + [ 3] 0x0000000000400529 + [ 4] 0x000000000040052d + [ 5] 0x0000000000400521 + [ 6] 0x0000000000400525 + [ 7] 0x0000000000400529 + [ 8] 0x000000000040052d + [ 9] 0x0000000000400521 + [10] 0x0000000000400525 + [11] 0x0000000000400529 + [12] 0x000000000040052d + [13] 0x0000000000400521 + [14] 0x0000000000400525 + [15] 0x0000000000400529 + [16] 0x000000000040052d + [17] 0x0000000000400521 + [18] 0x0000000000400525 + [19] 0x0000000000400529 + [20] 0x000000000040052d''']) + + #remove + shutil.rmtree(os.path.join(self.getSourceDir(), "intelpt-trace", "trace_copy_dir"))