forked from OSchip/llvm-project
[trace] [intel pt] Create a "process trace save" command
added new command "process trace save -d <directory>". -it saves a JSON file as <directory>/trace.json, with the main properties of the trace session. -it saves binary Intel-pt trace as <directory>/thread_id.trace; each file saves each thread. -it saves modules to the directory <directory>/modules . -it only works for live process and it only support Intel-pt right now. Example: ``` b main run process trace start n process trace save -d /tmp/mytrace ``` A file named trace.json and xxx.trace should be generated in /tmp/mytrace. To load the trace that was just saved: ``` trace load /tmp/mytrace thread trace dump instructions ``` You should see the instructions of the trace got printed. To run a test: ``` cd ~/llvm-sand/build/Release/fbcode-x86_64/toolchain ninja lldb-dotest ./bin/lldb-dotest -p TestTraceSave ``` Reviewed By: wallace Differential Revision: https://reviews.llvm.org/D107669
This commit is contained in:
parent
8e284be04f
commit
602497d672
|
@ -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 @@ public:
|
|||
/// 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 <directory>/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 @@ public:
|
|||
|
||||
/// 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.
|
||||
|
|
|
@ -1644,6 +1644,80 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
// 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<OptionDefinition> 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 <directory>/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 [<cmd-options>]",
|
||||
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 @@ public:
|
|||
: CommandObjectMultiword(
|
||||
interpreter, "trace", "Commands for tracing the current process.",
|
||||
"process trace <subcommand> [<subcommand objects>]") {
|
||||
LoadSubCommand("save", CommandObjectSP(
|
||||
new CommandObjectProcessTraceSave(interpreter)));
|
||||
LoadSubCommand("start", CommandObjectSP(new CommandObjectProcessTraceStart(
|
||||
interpreter)));
|
||||
LoadSubCommand("stop", CommandObjectSP(
|
||||
|
|
|
@ -744,6 +744,14 @@ let Command = "process save_core" in {
|
|||
"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. "
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
add_lldb_library(lldbPluginTraceCommon
|
||||
ThreadPostMortemTrace.cpp
|
||||
TraceJSONStructs.cpp
|
||||
TraceSessionFileParser.cpp
|
||||
TraceSessionSaver.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
|
|
|
@ -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 <sstream>
|
||||
|
||||
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<StringRef> 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
|
|
@ -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<std::string> file;
|
||||
JSONAddress load_address;
|
||||
llvm::Optional<std::string> uuid;
|
||||
};
|
||||
|
||||
struct JSONThread {
|
||||
int64_t tid;
|
||||
std::string trace_file;
|
||||
};
|
||||
|
||||
struct JSONProcess {
|
||||
int64_t pid;
|
||||
std::string triple;
|
||||
std::vector<JSONThread> threads;
|
||||
std::vector<JSONModule> modules;
|
||||
};
|
||||
|
||||
struct JSONTracePluginSettings {
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct JSONTraceSessionBase {
|
||||
std::vector<JSONProcess> processes;
|
||||
};
|
||||
|
||||
/// The trace plug-in implementation should provide its own TPluginSettings,
|
||||
/// which corresponds to the "trace" section of the schema.
|
||||
template <class TPluginSettings>
|
||||
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 <class TPluginSettings>
|
||||
bool fromJSON(const Value &value,
|
||||
lldb_private::JSONTraceSession<TPluginSettings> &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
|
|
@ -170,55 +170,3 @@ TraceSessionFileParser::ParseCommonSessionFile(
|
|||
}
|
||||
return parsed_processes;
|
||||
}
|
||||
|
||||
namespace llvm {
|
||||
namespace json {
|
||||
|
||||
bool fromJSON(const Value &value, TraceSessionFileParser::JSONAddress &address,
|
||||
Path path) {
|
||||
Optional<StringRef> 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
|
||||
|
|
|
@ -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 @@ namespace lldb_private {
|
|||
/// 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<std::string> file;
|
||||
JSONAddress load_address;
|
||||
llvm::Optional<std::string> uuid;
|
||||
};
|
||||
|
||||
struct JSONThread {
|
||||
int64_t tid;
|
||||
std::string trace_file;
|
||||
};
|
||||
|
||||
struct JSONProcess {
|
||||
int64_t pid;
|
||||
std::string triple;
|
||||
std::vector<JSONThread> threads;
|
||||
std::vector<JSONModule> modules;
|
||||
};
|
||||
|
||||
struct JSONTracePluginSettings {
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct JSONTraceSessionBase {
|
||||
std::vector<JSONProcess> processes;
|
||||
};
|
||||
|
||||
/// The trace plug-in implementation should provide its own TPluginSettings,
|
||||
/// which corresponds to the "trace" section of the schema.
|
||||
template <class TPluginSettings>
|
||||
struct JSONTraceSession : JSONTraceSessionBase {
|
||||
TPluginSettings trace;
|
||||
};
|
||||
/// \}
|
||||
|
||||
/// Helper struct holding the objects created when parsing a process
|
||||
struct ParsedProcess {
|
||||
|
@ -130,50 +89,5 @@ protected:
|
|||
};
|
||||
} // 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 <class TPluginSettings>
|
||||
bool fromJSON(
|
||||
const Value &value,
|
||||
lldb_private::TraceSessionFileParser::JSONTraceSession<TPluginSettings>
|
||||
&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
|
||||
|
|
|
@ -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 <fstream>
|
||||
|
||||
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<JSONTraceSessionBase> TraceSessionSaver::BuildProcessesSection(
|
||||
Process &live_process,
|
||||
std::function<
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(lldb::tid_t tid)>
|
||||
raw_trace_fetcher,
|
||||
FileSpec directory) {
|
||||
|
||||
JSONTraceSessionBase json_session_description;
|
||||
Expected<std::vector<JSONThread>> json_threads =
|
||||
BuildThreadsSection(live_process, raw_trace_fetcher, directory);
|
||||
if (!json_threads)
|
||||
return json_threads.takeError();
|
||||
|
||||
Expected<std::vector<JSONModule>> json_modules =
|
||||
BuildModulesSection(live_process, directory);
|
||||
if (!json_modules)
|
||||
return json_modules.takeError();
|
||||
|
||||
json_session_description.processes.push_back(JSONProcess{
|
||||
static_cast<int64_t>(live_process.GetID()),
|
||||
live_process.GetTarget().GetArchitecture().GetTriple().getTriple(),
|
||||
json_threads.get(), json_modules.get()});
|
||||
return json_session_description;
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<JSONThread>> TraceSessionSaver::BuildThreadsSection(
|
||||
Process &live_process,
|
||||
std::function<
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(lldb::tid_t tid)>
|
||||
raw_trace_fetcher,
|
||||
FileSpec directory) {
|
||||
std::vector<JSONThread> 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<int64_t>(thread_sp->GetID()),
|
||||
raw_trace_path.GetPath().c_str()});
|
||||
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>> raw_trace =
|
||||
raw_trace_fetcher(thread_sp->GetID());
|
||||
|
||||
if (!raw_trace)
|
||||
return raw_trace.takeError();
|
||||
if (!raw_trace.get())
|
||||
continue;
|
||||
|
||||
std::basic_fstream<char> raw_trace_fs = std::fstream(
|
||||
raw_trace_path.GetPath().c_str(), std::ios::out | std::ios::binary);
|
||||
raw_trace_fs.write(reinterpret_cast<const char *>(&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<std::vector<JSONModule>>
|
||||
TraceSessionSaver::BuildModulesSection(Process &live_process,
|
||||
FileSpec directory) {
|
||||
std::vector<JSONModule> 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;
|
||||
}
|
|
@ -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<JSONTraceSessionBase> BuildProcessesSection(
|
||||
Process &live_process,
|
||||
std::function<
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(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<std::vector<JSONThread>> BuildThreadsSection(
|
||||
Process &live_process,
|
||||
std::function<
|
||||
llvm::Expected<llvm::Optional<std::vector<uint8_t>>>(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 <directory/modules> 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 <directory>/modules/usr/lib/foo/libbar.so.
|
||||
///
|
||||
/// \return
|
||||
/// The modules section or \a llvm::Error in case of failures.
|
||||
static llvm::Expected<std::vector<JSONModule>>
|
||||
BuildModulesSection(Process &live_process, FileSpec directory);
|
||||
};
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_TARGET_TRACESESSIONSAVER_H
|
|
@ -19,7 +19,9 @@ add_lldb_library(lldbPluginTraceIntelPT PLUGIN
|
|||
IntelPTDecoder.cpp
|
||||
TraceCursorIntelPT.cpp
|
||||
TraceIntelPT.cpp
|
||||
TraceIntelPTJSONStructs.cpp
|
||||
TraceIntelPTSessionFileParser.cpp
|
||||
TraceIntelPTSessionSaver.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
|
|
|
@ -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 @@ uint32_t TraceIntelPT::GetPluginVersion() { return 1; }
|
|||
|
||||
void TraceIntelPT::Dump(Stream *s) const {}
|
||||
|
||||
llvm::Error TraceIntelPT::SaveLiveTraceToDisk(FileSpec directory) {
|
||||
RefreshLiveProcessState();
|
||||
return TraceIntelPTSessionSaver().SaveToDisk(*this, directory);
|
||||
}
|
||||
|
||||
Expected<TraceSP> TraceIntelPT::CreateInstanceForSessionFile(
|
||||
const json::Value &trace_session_file, StringRef session_file_dir,
|
||||
Debugger &debugger) {
|
||||
|
@ -86,7 +93,8 @@ TraceIntelPT::TraceIntelPT(
|
|||
: m_cpu_info(cpu_info) {
|
||||
for (const ThreadPostMortemTraceSP &thread : traced_threads)
|
||||
m_thread_decoders.emplace(
|
||||
thread.get(), std::make_unique<PostMortemThreadDecoder>(thread, *this));
|
||||
thread->GetID(),
|
||||
std::make_unique<PostMortemThreadDecoder>(thread, *this));
|
||||
}
|
||||
|
||||
DecodedThreadSP TraceIntelPT::Decode(Thread &thread) {
|
||||
|
@ -96,7 +104,7 @@ DecodedThreadSP TraceIntelPT::Decode(Thread &thread) {
|
|||
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<DecodedThread>(
|
||||
thread.shared_from_this(),
|
||||
|
@ -120,7 +128,7 @@ void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose) {
|
|||
}
|
||||
|
||||
Optional<size_t> TraceIntelPT::GetRawTraceSize(Thread &thread) {
|
||||
if (IsTraced(thread))
|
||||
if (IsTraced(thread.GetID()))
|
||||
return Decode(thread)->GetRawTraceSize();
|
||||
else
|
||||
return None;
|
||||
|
@ -188,6 +196,8 @@ Expected<pt_cpu> TraceIntelPT::GetCPUInfo() {
|
|||
return *m_cpu_info;
|
||||
}
|
||||
|
||||
Process *TraceIntelPT::GetLiveProcess() { return m_live_process; }
|
||||
|
||||
void TraceIntelPT::DoRefreshLiveProcessState(
|
||||
Expected<TraceGetStateResponse> state) {
|
||||
m_thread_decoders.clear();
|
||||
|
@ -201,13 +211,13 @@ void TraceIntelPT::DoRefreshLiveProcessState(
|
|||
Thread &thread =
|
||||
*m_live_process->GetThreadList().FindThreadByID(thread_state.tid);
|
||||
m_thread_decoders.emplace(
|
||||
&thread, std::make_unique<LiveThreadDecoder>(thread, *this));
|
||||
thread_state.tid, std::make_unique<LiveThreadDecoder>(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
|
||||
|
|
|
@ -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 @@ class TraceIntelPT : public Trace {
|
|||
public:
|
||||
void Dump(Stream *s) const override;
|
||||
|
||||
llvm::Error SaveLiveTraceToDisk(FileSpec directory) override;
|
||||
|
||||
~TraceIntelPT() override = default;
|
||||
|
||||
/// PluginInterface protocol
|
||||
|
@ -74,7 +79,7 @@ public:
|
|||
void DoRefreshLiveProcessState(
|
||||
llvm::Expected<TraceGetStateResponse> state) override;
|
||||
|
||||
bool IsTraced(const Thread &thread) override;
|
||||
bool IsTraced(lldb::tid_t tid) override;
|
||||
|
||||
const char *GetStartConfigurationHelp() override;
|
||||
|
||||
|
@ -139,6 +144,13 @@ public:
|
|||
|
||||
llvm::Expected<pt_cpu> 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 @@ private:
|
|||
/// It is provided by either a session file or a live process' "cpuInfo"
|
||||
/// binary data.
|
||||
llvm::Optional<pt_cpu> m_cpu_info;
|
||||
std::map<const Thread *, std::unique_ptr<ThreadDecoder>> m_thread_decoders;
|
||||
std::map<lldb::tid_t, std::unique_ptr<ThreadDecoder>> m_thread_decoders;
|
||||
/// Error gotten after a failed live process update, if any.
|
||||
llvm::Optional<std::string> m_live_refresh_error;
|
||||
};
|
||||
|
|
|
@ -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 <string>
|
||||
|
||||
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
|
|
@ -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 <intel-pt.h>
|
||||
|
||||
namespace lldb_private {
|
||||
namespace trace_intel_pt {
|
||||
|
||||
struct JSONTraceIntelPTCPUInfo {
|
||||
JSONTraceIntelPTCPUInfo() = default;
|
||||
|
||||
JSONTraceIntelPTCPUInfo(pt_cpu cpu_info) {
|
||||
family = static_cast<int64_t>(cpu_info.family);
|
||||
model = static_cast<int64_t>(cpu_info.model);
|
||||
stepping = static_cast<int64_t>(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
|
|
@ -72,3 +72,13 @@ let Command = "process trace start intel pt" in {
|
|||
"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.">;
|
||||
}
|
||||
|
|
|
@ -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 @@ TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance(
|
|||
|
||||
Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() {
|
||||
json::Path::Root root("traceSession");
|
||||
TraceSessionFileParser::JSONTraceSession<JSONTraceIntelPTSettings> session;
|
||||
JSONTraceSession<JSONTraceIntelPTSettings> session;
|
||||
if (!json::fromJSON(m_trace_session_file, session, root))
|
||||
return CreateJSONError(root, m_trace_session_file);
|
||||
|
||||
|
@ -70,38 +67,3 @@ Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() {
|
|||
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
|
||||
|
|
|
@ -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 TraceIntelPT;
|
|||
|
||||
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 @@ private:
|
|||
} // 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
|
||||
|
|
|
@ -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 <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
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<JSONTraceIntelPTTrace> json_intel_pt_trace =
|
||||
BuildTraceSection(trace_ipt);
|
||||
if (!json_intel_pt_trace)
|
||||
return json_intel_pt_trace.takeError();
|
||||
|
||||
llvm::Expected<JSONTraceSessionBase> json_session_description =
|
||||
TraceSessionSaver::BuildProcessesSection(
|
||||
*live_process,
|
||||
[&](lldb::tid_t tid)
|
||||
-> llvm::Expected<llvm::Optional<std::vector<uint8_t>>> {
|
||||
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<JSONTraceIntelPTTrace>
|
||||
TraceIntelPTSessionSaver::BuildTraceSection(TraceIntelPT &trace_ipt) {
|
||||
llvm::Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();
|
||||
if (!cpu_info)
|
||||
return cpu_info.takeError();
|
||||
|
||||
return JSONTraceIntelPTTrace{"intel-pt",
|
||||
JSONTraceIntelPTCPUInfo(cpu_info.get())};
|
||||
}
|
|
@ -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 <directory>/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<JSONTraceIntelPTTrace>
|
||||
BuildTraceSection(TraceIntelPT &trace_ipt);
|
||||
};
|
||||
|
||||
} // namespace trace_intel_pt
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONSAVER_H
|
|
@ -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 <trace_copy_dir>
|
||||
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)
|
Loading…
Reference in New Issue