435 lines
15 KiB
C++
435 lines
15 KiB
C++
//===-- TraceIntelPTBundleLoader.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 "TraceIntelPTBundleLoader.h"
|
|
|
|
#include "../common/ThreadPostMortemTrace.h"
|
|
#include "TraceIntelPT.h"
|
|
#include "TraceIntelPTConstants.h"
|
|
#include "TraceIntelPTJSONStructs.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/Target.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::trace_intel_pt;
|
|
using namespace llvm;
|
|
|
|
FileSpec TraceIntelPTBundleLoader::NormalizePath(const std::string &path) {
|
|
FileSpec file_spec(path);
|
|
if (file_spec.IsRelative())
|
|
file_spec.PrependPathComponent(m_bundle_dir);
|
|
return file_spec;
|
|
}
|
|
|
|
Error TraceIntelPTBundleLoader::ParseModule(Target &target,
|
|
const JSONModule &module) {
|
|
auto do_parse = [&]() -> Error {
|
|
FileSpec system_file_spec(module.system_path);
|
|
|
|
FileSpec local_file_spec(module.file.has_value() ? *module.file
|
|
: module.system_path);
|
|
|
|
ModuleSpec module_spec;
|
|
module_spec.GetFileSpec() = local_file_spec;
|
|
module_spec.GetPlatformFileSpec() = system_file_spec;
|
|
|
|
if (module.uuid.has_value())
|
|
module_spec.GetUUID().SetFromStringRef(*module.uuid);
|
|
|
|
Status error;
|
|
ModuleSP module_sp =
|
|
target.GetOrCreateModule(module_spec, /*notify*/ false, &error);
|
|
|
|
if (error.Fail())
|
|
return error.ToError();
|
|
|
|
bool load_addr_changed = false;
|
|
module_sp->SetLoadAddress(target, module.load_address.value, false,
|
|
load_addr_changed);
|
|
return Error::success();
|
|
};
|
|
if (Error err = do_parse())
|
|
return createStringError(
|
|
inconvertibleErrorCode(), "Error when parsing module %s. %s",
|
|
module.system_path.c_str(), toString(std::move(err)).c_str());
|
|
return Error::success();
|
|
}
|
|
|
|
Error TraceIntelPTBundleLoader::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(), GetSchema().data());
|
|
}
|
|
|
|
ThreadPostMortemTraceSP
|
|
TraceIntelPTBundleLoader::ParseThread(Process &process,
|
|
const JSONThread &thread) {
|
|
lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid);
|
|
|
|
Optional<FileSpec> trace_file;
|
|
if (thread.ipt_trace)
|
|
trace_file = FileSpec(*thread.ipt_trace);
|
|
|
|
ThreadPostMortemTraceSP thread_sp =
|
|
std::make_shared<ThreadPostMortemTrace>(process, tid, trace_file);
|
|
process.GetThreadList().AddThread(thread_sp);
|
|
return thread_sp;
|
|
}
|
|
|
|
Expected<TraceIntelPTBundleLoader::ParsedProcess>
|
|
TraceIntelPTBundleLoader::CreateEmptyProcess(lldb::pid_t pid,
|
|
llvm::StringRef triple) {
|
|
TargetSP target_sp;
|
|
Status error = m_debugger.GetTargetList().CreateTarget(
|
|
m_debugger, /*user_exe_path*/ StringRef(), triple, eLoadDependentsNo,
|
|
/*platform_options*/ nullptr, target_sp);
|
|
|
|
if (!target_sp)
|
|
return error.ToError();
|
|
|
|
ParsedProcess parsed_process;
|
|
parsed_process.target_sp = target_sp;
|
|
|
|
ProcessSP process_sp = target_sp->CreateProcess(
|
|
/*listener*/ nullptr, "trace",
|
|
/*crash_file*/ nullptr,
|
|
/*can_connect*/ false);
|
|
|
|
process_sp->SetID(static_cast<lldb::pid_t>(pid));
|
|
|
|
return parsed_process;
|
|
}
|
|
|
|
Expected<TraceIntelPTBundleLoader::ParsedProcess>
|
|
TraceIntelPTBundleLoader::ParseProcess(const JSONProcess &process) {
|
|
Expected<ParsedProcess> parsed_process =
|
|
CreateEmptyProcess(process.pid, process.triple.value_or(""));
|
|
|
|
if (!parsed_process)
|
|
return parsed_process.takeError();
|
|
|
|
ProcessSP process_sp = parsed_process->target_sp->GetProcessSP();
|
|
|
|
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(*parsed_process->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<TraceIntelPTBundleLoader::ParsedProcess>
|
|
TraceIntelPTBundleLoader::ParseKernel(
|
|
const JSONTraceBundleDescription &bundle_description) {
|
|
Expected<ParsedProcess> parsed_process =
|
|
CreateEmptyProcess(kDefaultKernelProcessID, "");
|
|
|
|
if (!parsed_process)
|
|
return parsed_process.takeError();
|
|
|
|
ProcessSP process_sp = parsed_process->target_sp->GetProcessSP();
|
|
|
|
// Add cpus as fake threads
|
|
for (const JSONCpu &cpu : *bundle_description.cpus) {
|
|
ThreadPostMortemTraceSP thread_sp = std::make_shared<ThreadPostMortemTrace>(
|
|
*process_sp, static_cast<lldb::tid_t>(cpu.id), FileSpec(cpu.ipt_trace));
|
|
thread_sp->SetName(formatv("kernel_cpu_{0}", cpu.id).str().c_str());
|
|
process_sp->GetThreadList().AddThread(thread_sp);
|
|
parsed_process->threads.push_back(thread_sp);
|
|
}
|
|
|
|
// Add kernel image
|
|
FileSpec file_spec(bundle_description.kernel->file);
|
|
ModuleSpec module_spec;
|
|
module_spec.GetFileSpec() = file_spec;
|
|
|
|
Status error;
|
|
ModuleSP module_sp =
|
|
parsed_process->target_sp->GetOrCreateModule(module_spec, false, &error);
|
|
|
|
if (error.Fail())
|
|
return error.ToError();
|
|
|
|
lldb::addr_t load_address =
|
|
bundle_description.kernel->load_address
|
|
? bundle_description.kernel->load_address->value
|
|
: kDefaultKernelLoadAddress;
|
|
|
|
bool load_addr_changed = false;
|
|
module_sp->SetLoadAddress(*parsed_process->target_sp, load_address, false,
|
|
load_addr_changed);
|
|
|
|
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<TraceIntelPTBundleLoader::ParsedProcess>>
|
|
TraceIntelPTBundleLoader::LoadBundle(
|
|
const JSONTraceBundleDescription &bundle_description) {
|
|
std::vector<ParsedProcess> parsed_processes;
|
|
|
|
auto HandleError = [&](Error &&err) {
|
|
// 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);
|
|
return std::move(err);
|
|
};
|
|
|
|
if (bundle_description.processes) {
|
|
for (const JSONProcess &process : *bundle_description.processes) {
|
|
if (Expected<ParsedProcess> parsed_process = ParseProcess(process))
|
|
parsed_processes.push_back(std::move(*parsed_process));
|
|
else
|
|
return HandleError(parsed_process.takeError());
|
|
}
|
|
}
|
|
|
|
if (bundle_description.kernel) {
|
|
if (Expected<ParsedProcess> kernel_process =
|
|
ParseKernel(bundle_description))
|
|
parsed_processes.push_back(std::move(*kernel_process));
|
|
else
|
|
return HandleError(kernel_process.takeError());
|
|
}
|
|
|
|
return parsed_processes;
|
|
}
|
|
|
|
StringRef TraceIntelPTBundleLoader::GetSchema() {
|
|
static std::string schema;
|
|
if (schema.empty()) {
|
|
schema = R"({
|
|
"type": "intel-pt",
|
|
"cpuInfo": {
|
|
// CPU information gotten from, for example, /proc/cpuinfo.
|
|
|
|
"vendor": "GenuineIntel" | "unknown",
|
|
"family": integer,
|
|
"model": integer,
|
|
"stepping": integer
|
|
},
|
|
"processes?": [
|
|
{
|
|
"pid": integer,
|
|
"triple"?: string,
|
|
// Optional clang/llvm target triple.
|
|
// This must be provided if the trace will be created not using the
|
|
// CLI or on a machine other than where the target was traced.
|
|
"threads": [
|
|
// A list of known threads for the given process. When context switch
|
|
// data is provided, LLDB will automatically create threads for the
|
|
// this process whenever it finds new threads when traversing the
|
|
// context switches, so passing values to this list in this case is
|
|
// optional.
|
|
{
|
|
"tid": integer,
|
|
"iptTrace"?: string
|
|
// Path to the raw Intel PT buffer file for this thread.
|
|
}
|
|
],
|
|
"modules": [
|
|
{
|
|
"systemPath": string,
|
|
// Original path of the module at runtime.
|
|
"file"?: string,
|
|
// Path to a copy of the file if not available at "systemPath".
|
|
"loadAddress": integer | string decimal | hex string,
|
|
// Lowest address of the sections of the module loaded on memory.
|
|
"uuid"?: string,
|
|
// Build UUID for the file for sanity checks.
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"cpus"?: [
|
|
{
|
|
"id": integer,
|
|
// Id of this CPU core.
|
|
"iptTrace": string,
|
|
// Path to the raw Intel PT buffer for this cpu core.
|
|
"contextSwitchTrace": string,
|
|
// Path to the raw perf_event_open context switch trace file for this cpu core.
|
|
// The perf_event must have been configured with PERF_SAMPLE_TID and
|
|
// PERF_SAMPLE_TIME, as well as sample_id_all = 1.
|
|
}
|
|
],
|
|
"tscPerfZeroConversion"?: {
|
|
// Values used to convert between TSCs and nanoseconds. See the time_zero
|
|
// section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html
|
|
// for for information.
|
|
|
|
"timeMult": integer,
|
|
"timeShift": integer,
|
|
"timeZero": integer | string decimal | hex string,
|
|
},
|
|
"kernel"?: {
|
|
"loadAddress"?: integer | string decimal | hex string,
|
|
// Kernel's image load address. Defaults to 0xffffffff81000000, which
|
|
// is a load address of x86 architecture if KASLR is not enabled.
|
|
"file": string,
|
|
// Path to the kernel image.
|
|
}
|
|
}
|
|
|
|
Notes:
|
|
|
|
- All paths are either absolute or relative to folder containing the bundle
|
|
description file.
|
|
- "cpus" is provided if and only if processes[].threads[].iptTrace is not provided.
|
|
- "tscPerfZeroConversion" must be provided if "cpus" is provided.
|
|
- If "kernel" is provided, then the "processes" section must be empty or not
|
|
passed at all, and the "cpus" section must be provided. This configuration
|
|
indicates that the kernel was traced and user processes weren't. Besides
|
|
that, the kernel is treated as a single process with one thread per CPU
|
|
core. This doesn't handle actual kernel threads, but instead treats
|
|
all the instructions executed by the kernel on each core as an
|
|
individual thread.})";
|
|
}
|
|
return schema;
|
|
}
|
|
|
|
Error TraceIntelPTBundleLoader::AugmentThreadsFromContextSwitches(
|
|
JSONTraceBundleDescription &bundle_description) {
|
|
if (!bundle_description.cpus || !bundle_description.processes)
|
|
return Error::success();
|
|
|
|
if (!bundle_description.tsc_perf_zero_conversion)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"TSC to nanos conversion values are needed when "
|
|
"context switch information is provided.");
|
|
|
|
DenseMap<lldb::pid_t, JSONProcess *> indexed_processes;
|
|
DenseMap<JSONProcess *, DenseSet<tid_t>> indexed_threads;
|
|
|
|
for (JSONProcess &process : *bundle_description.processes) {
|
|
indexed_processes[process.pid] = &process;
|
|
for (JSONThread &thread : process.threads)
|
|
indexed_threads[&process].insert(thread.tid);
|
|
}
|
|
|
|
auto on_thread_seen = [&](lldb::pid_t pid, tid_t tid) {
|
|
auto proc = indexed_processes.find(pid);
|
|
if (proc == indexed_processes.end())
|
|
return;
|
|
if (indexed_threads[proc->second].count(tid))
|
|
return;
|
|
indexed_threads[proc->second].insert(tid);
|
|
proc->second->threads.push_back({tid, /*ipt_trace=*/None});
|
|
};
|
|
|
|
for (const JSONCpu &cpu : *bundle_description.cpus) {
|
|
Error err = Trace::OnDataFileRead(
|
|
FileSpec(cpu.context_switch_trace),
|
|
[&](ArrayRef<uint8_t> data) -> Error {
|
|
Expected<std::vector<ThreadContinuousExecution>> executions =
|
|
DecodePerfContextSwitchTrace(
|
|
data, cpu.id, *bundle_description.tsc_perf_zero_conversion);
|
|
if (!executions)
|
|
return executions.takeError();
|
|
for (const ThreadContinuousExecution &execution : *executions)
|
|
on_thread_seen(execution.pid, execution.tid);
|
|
return Error::success();
|
|
});
|
|
if (err)
|
|
return err;
|
|
}
|
|
return Error::success();
|
|
}
|
|
|
|
Expected<TraceSP> TraceIntelPTBundleLoader::CreateTraceIntelPTInstance(
|
|
JSONTraceBundleDescription &bundle_description,
|
|
std::vector<ParsedProcess> &parsed_processes) {
|
|
std::vector<ThreadPostMortemTraceSP> threads;
|
|
std::vector<ProcessSP> processes;
|
|
for (const ParsedProcess &parsed_process : parsed_processes) {
|
|
processes.push_back(parsed_process.target_sp->GetProcessSP());
|
|
threads.insert(threads.end(), parsed_process.threads.begin(),
|
|
parsed_process.threads.end());
|
|
}
|
|
|
|
TraceIntelPT::TraceMode trace_mode = bundle_description.kernel
|
|
? TraceIntelPT::TraceMode::KernelMode
|
|
: TraceIntelPT::TraceMode::UserMode;
|
|
|
|
TraceSP trace_instance = TraceIntelPT::CreateInstanceForPostmortemTrace(
|
|
bundle_description, processes, threads, trace_mode);
|
|
for (const ParsedProcess &parsed_process : parsed_processes)
|
|
parsed_process.target_sp->SetTrace(trace_instance);
|
|
|
|
return trace_instance;
|
|
}
|
|
|
|
void TraceIntelPTBundleLoader::NormalizeAllPaths(
|
|
JSONTraceBundleDescription &bundle_description) {
|
|
if (bundle_description.processes) {
|
|
for (JSONProcess &process : *bundle_description.processes) {
|
|
for (JSONModule &module : process.modules) {
|
|
module.system_path = NormalizePath(module.system_path).GetPath();
|
|
if (module.file)
|
|
module.file = NormalizePath(*module.file).GetPath();
|
|
}
|
|
for (JSONThread &thread : process.threads) {
|
|
if (thread.ipt_trace)
|
|
thread.ipt_trace = NormalizePath(*thread.ipt_trace).GetPath();
|
|
}
|
|
}
|
|
}
|
|
if (bundle_description.cpus) {
|
|
for (JSONCpu &cpu : *bundle_description.cpus) {
|
|
cpu.context_switch_trace =
|
|
NormalizePath(cpu.context_switch_trace).GetPath();
|
|
cpu.ipt_trace = NormalizePath(cpu.ipt_trace).GetPath();
|
|
}
|
|
}
|
|
if (bundle_description.kernel) {
|
|
bundle_description.kernel->file =
|
|
NormalizePath(bundle_description.kernel->file).GetPath();
|
|
}
|
|
}
|
|
|
|
Expected<TraceSP> TraceIntelPTBundleLoader::Load() {
|
|
json::Path::Root root("traceBundle");
|
|
JSONTraceBundleDescription bundle_description;
|
|
if (!fromJSON(m_bundle_description, bundle_description, root))
|
|
return CreateJSONError(root, m_bundle_description);
|
|
|
|
NormalizeAllPaths(bundle_description);
|
|
|
|
if (Error err = AugmentThreadsFromContextSwitches(bundle_description))
|
|
return std::move(err);
|
|
|
|
if (Expected<std::vector<ParsedProcess>> parsed_processes =
|
|
LoadBundle(bundle_description))
|
|
return CreateTraceIntelPTInstance(bundle_description, *parsed_processes);
|
|
else
|
|
return parsed_processes.takeError();
|
|
}
|