Depends on D89408. This diff finally implements trace decoding! The current interface is $ trace load /path/to/trace/session/file.json $ thread trace dump instructions thread #1: tid = 3842849, total instructions = 22 [ 0] 0x40052d [ 1] 0x40052d ... [19] 0x400521 $ # simply enter, which is a repeat command [20] 0x40052d [21] 0x400529 ... This doesn't do any disassembly, which will be done in the next diff. Changes: - Added an IntelPTDecoder class, that is a wrapper for libipt, which is the actual library that performs the decoding. - Added TraceThreadDecoder class that decodes traces and memoizes the result to avoid repeating the decoding step. - Added a DecodedThread class, which represents the output from decoding and that for the time being only stores the list of reconstructed instructions. Later it'll contain the function call hierarchy, which will enable reconstructing backtraces. - Added basic APIs for accessing the trace in Trace.h: - GetInstructionCount, which counts the number of instructions traced for a given thread - IsTraceFailed, which returns an Error if decoding a thread failed - ForEachInstruction, which iterates on the instructions traced for a given thread, concealing the internal storage of threads, as plug-ins can decide to generate the instructions on the fly or to store them all in a vector, like I do. - DumpTraceInstructions was updated to print the instructions or show an error message if decoding was impossible. - Tests included Differential Revision: https://reviews.llvm.org/D89283
225 lines
7.0 KiB
C++
225 lines
7.0 KiB
C++
//===-- TraceSessionFileParser.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 "lldb/Target/TraceSessionFileParser.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/ThreadTrace.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace llvm;
|
|
|
|
void TraceSessionFileParser::NormalizePath(lldb_private::FileSpec &file_spec) {
|
|
if (file_spec.IsRelative())
|
|
file_spec.PrependPathComponent(m_session_file_dir);
|
|
}
|
|
|
|
Error TraceSessionFileParser::ParseModule(lldb::TargetSP &target_sp,
|
|
const JSONModule &module) {
|
|
FileSpec system_file_spec(module.system_path);
|
|
NormalizePath(system_file_spec);
|
|
|
|
FileSpec local_file_spec(module.file.hasValue() ? *module.file
|
|
: module.system_path);
|
|
NormalizePath(local_file_spec);
|
|
|
|
ModuleSpec module_spec;
|
|
module_spec.GetFileSpec() = local_file_spec;
|
|
module_spec.GetPlatformFileSpec() = system_file_spec;
|
|
|
|
if (module.uuid.hasValue())
|
|
module_spec.GetUUID().SetFromStringRef(*module.uuid);
|
|
|
|
Status error;
|
|
ModuleSP module_sp =
|
|
target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error);
|
|
|
|
if (error.Fail())
|
|
return error.ToError();
|
|
|
|
bool load_addr_changed = false;
|
|
module_sp->SetLoadAddress(*target_sp, module.load_address.value, false,
|
|
load_addr_changed);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
Error TraceSessionFileParser::CreateJSONError(json::Path::Root &root,
|
|
const json::Value &value) {
|
|
std::string err;
|
|
raw_string_ostream os(err);
|
|
root.printErrorContext(value, os);
|
|
return createStringError(
|
|
std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s",
|
|
toString(root.getError()).c_str(), os.str().c_str(), m_schema.data());
|
|
}
|
|
|
|
std::string TraceSessionFileParser::BuildSchema(StringRef plugin_schema) {
|
|
std::ostringstream schema_builder;
|
|
schema_builder << "{\n \"trace\": ";
|
|
schema_builder << plugin_schema.data() << ",";
|
|
schema_builder << R"(
|
|
"processes": [
|
|
{
|
|
"pid": integer,
|
|
"triple": string, // llvm-triple
|
|
"threads": [
|
|
{
|
|
"tid": integer,
|
|
"traceFile": string
|
|
}
|
|
],
|
|
"modules": [
|
|
{
|
|
"systemPath": string, // original path of the module at runtime
|
|
"file"?: string, // copy of the file if not available at "systemPath"
|
|
"loadAddress": string, // string address in hex or decimal form
|
|
"uuid"?: string,
|
|
}
|
|
]
|
|
}
|
|
]
|
|
// Notes:
|
|
// All paths are either absolute or relative to the session file.
|
|
}
|
|
)";
|
|
return schema_builder.str();
|
|
}
|
|
|
|
ThreadTraceSP TraceSessionFileParser::ParseThread(ProcessSP &process_sp,
|
|
const JSONThread &thread) {
|
|
lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid);
|
|
|
|
FileSpec trace_file(thread.trace_file);
|
|
NormalizePath(trace_file);
|
|
|
|
ThreadTraceSP thread_sp =
|
|
std::make_shared<ThreadTrace>(*process_sp, tid, trace_file);
|
|
process_sp->GetThreadList().AddThread(thread_sp);
|
|
return thread_sp;
|
|
}
|
|
|
|
Expected<TraceSessionFileParser::ParsedProcess>
|
|
TraceSessionFileParser::ParseProcess(const JSONProcess &process) {
|
|
TargetSP target_sp;
|
|
Status error = m_debugger.GetTargetList().CreateTarget(
|
|
m_debugger, /*user_exe_path*/ StringRef(), process.triple,
|
|
eLoadDependentsNo,
|
|
/*platform_options*/ nullptr, target_sp);
|
|
|
|
if (!target_sp)
|
|
return error.ToError();
|
|
|
|
ParsedProcess parsed_process;
|
|
parsed_process.target_sp = target_sp;
|
|
|
|
m_debugger.GetTargetList().SetSelectedTarget(target_sp.get());
|
|
|
|
ProcessSP process_sp = target_sp->CreateProcess(
|
|
/*listener*/ nullptr, "trace",
|
|
/*crash_file*/ nullptr);
|
|
|
|
process_sp->SetID(static_cast<lldb::pid_t>(process.pid));
|
|
|
|
for (const JSONThread &thread : process.threads)
|
|
parsed_process.threads.push_back(ParseThread(process_sp, thread));
|
|
|
|
for (const JSONModule &module : process.modules)
|
|
if (Error err = ParseModule(target_sp, module))
|
|
return std::move(err);
|
|
|
|
if (!process.threads.empty())
|
|
process_sp->GetThreadList().SetSelectedThreadByIndexID(0);
|
|
|
|
// We invoke DidAttach to create a correct stopped state for the process and
|
|
// its threads.
|
|
ArchSpec process_arch;
|
|
process_sp->DidAttach(process_arch);
|
|
|
|
return parsed_process;
|
|
}
|
|
|
|
Expected<std::vector<TraceSessionFileParser::ParsedProcess>>
|
|
TraceSessionFileParser::ParseCommonSessionFile(
|
|
const JSONTraceSessionBase &session) {
|
|
std::vector<ParsedProcess> parsed_processes;
|
|
|
|
auto onError = [&]() {
|
|
// Delete all targets that were created so far in case of failures
|
|
for (ParsedProcess &parsed_process : parsed_processes)
|
|
m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp);
|
|
};
|
|
|
|
for (const JSONProcess &process : session.processes) {
|
|
if (Expected<ParsedProcess> parsed_process = ParseProcess(process))
|
|
parsed_processes.push_back(std::move(*parsed_process));
|
|
else {
|
|
onError();
|
|
return parsed_process.takeError();
|
|
}
|
|
}
|
|
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
|