aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp')
-rw-r--r--contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp437
1 files changed, 437 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp
new file mode 100644
index 000000000000..1a9f6fe30509
--- /dev/null
+++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp
@@ -0,0 +1,437 @@
+//===-- 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/ProcessTrace.h"
+#include "lldb/Target/Target.h"
+#include <optional>
+
+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);
+
+ std::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;
+
+ ProcessTrace::Initialize();
+ 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 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=*/std::nullopt});
+ };
+
+ 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();
+}