1//===-- TraceIntelPTBundleLoader.cpp --------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "TraceIntelPTBundleLoader.h"
10
11#include "../common/ThreadPostMortemTrace.h"
12#include "TraceIntelPT.h"
13#include "TraceIntelPTConstants.h"
14#include "TraceIntelPTJSONStructs.h"
15#include "lldb/Core/Debugger.h"
16#include "lldb/Core/Module.h"
17#include "lldb/Target/Process.h"
18#include "lldb/Target/ProcessTrace.h"
19#include "lldb/Target/Target.h"
20#include <optional>
21
22using namespace lldb;
23using namespace lldb_private;
24using namespace lldb_private::trace_intel_pt;
25using namespace llvm;
26
27FileSpec TraceIntelPTBundleLoader::NormalizePath(const std::string &path) {
28  FileSpec file_spec(path);
29  if (file_spec.IsRelative())
30    file_spec.PrependPathComponent(m_bundle_dir);
31  return file_spec;
32}
33
34Error TraceIntelPTBundleLoader::ParseModule(Target &target,
35                                            const JSONModule &module) {
36  auto do_parse = [&]() -> Error {
37    FileSpec system_file_spec(module.system_path);
38
39    FileSpec local_file_spec(module.file.has_value() ? *module.file
40                                                     : module.system_path);
41
42    ModuleSpec module_spec;
43    module_spec.GetFileSpec() = local_file_spec;
44    module_spec.GetPlatformFileSpec() = system_file_spec;
45
46    if (module.uuid.has_value())
47      module_spec.GetUUID().SetFromStringRef(*module.uuid);
48
49    Status error;
50    ModuleSP module_sp =
51        target.GetOrCreateModule(module_spec, /*notify*/ false, &error);
52
53    if (error.Fail())
54      return error.ToError();
55
56    bool load_addr_changed = false;
57    module_sp->SetLoadAddress(target, module.load_address.value, false,
58                              load_addr_changed);
59    return Error::success();
60  };
61  if (Error err = do_parse())
62    return createStringError(
63        inconvertibleErrorCode(), "Error when parsing module %s. %s",
64        module.system_path.c_str(), toString(std::move(err)).c_str());
65  return Error::success();
66}
67
68Error TraceIntelPTBundleLoader::CreateJSONError(json::Path::Root &root,
69                                                const json::Value &value) {
70  std::string err;
71  raw_string_ostream os(err);
72  root.printErrorContext(value, os);
73  return createStringError(
74      std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s",
75      toString(root.getError()).c_str(), os.str().c_str(), GetSchema().data());
76}
77
78ThreadPostMortemTraceSP
79TraceIntelPTBundleLoader::ParseThread(Process &process,
80                                      const JSONThread &thread) {
81  lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid);
82
83  std::optional<FileSpec> trace_file;
84  if (thread.ipt_trace)
85    trace_file = FileSpec(*thread.ipt_trace);
86
87  ThreadPostMortemTraceSP thread_sp =
88      std::make_shared<ThreadPostMortemTrace>(process, tid, trace_file);
89  process.GetThreadList().AddThread(thread_sp);
90  return thread_sp;
91}
92
93Expected<TraceIntelPTBundleLoader::ParsedProcess>
94TraceIntelPTBundleLoader::CreateEmptyProcess(lldb::pid_t pid,
95                                             llvm::StringRef triple) {
96  TargetSP target_sp;
97  Status error = m_debugger.GetTargetList().CreateTarget(
98      m_debugger, /*user_exe_path*/ StringRef(), triple, eLoadDependentsNo,
99      /*platform_options*/ nullptr, target_sp);
100
101  if (!target_sp)
102    return error.ToError();
103
104  ParsedProcess parsed_process;
105  parsed_process.target_sp = target_sp;
106
107  ProcessTrace::Initialize();
108  ProcessSP process_sp = target_sp->CreateProcess(
109      /*listener*/ nullptr, "trace",
110      /*crash_file*/ nullptr,
111      /*can_connect*/ false);
112
113  process_sp->SetID(static_cast<lldb::pid_t>(pid));
114
115  return parsed_process;
116}
117
118Expected<TraceIntelPTBundleLoader::ParsedProcess>
119TraceIntelPTBundleLoader::ParseProcess(const JSONProcess &process) {
120  Expected<ParsedProcess> parsed_process =
121      CreateEmptyProcess(process.pid, process.triple.value_or(""));
122
123  if (!parsed_process)
124    return parsed_process.takeError();
125
126  ProcessSP process_sp = parsed_process->target_sp->GetProcessSP();
127
128  for (const JSONThread &thread : process.threads)
129    parsed_process->threads.push_back(ParseThread(*process_sp, thread));
130
131  for (const JSONModule &module : process.modules)
132    if (Error err = ParseModule(*parsed_process->target_sp, module))
133      return std::move(err);
134
135  if (!process.threads.empty())
136    process_sp->GetThreadList().SetSelectedThreadByIndexID(0);
137
138  // We invoke DidAttach to create a correct stopped state for the process and
139  // its threads.
140  ArchSpec process_arch;
141  process_sp->DidAttach(process_arch);
142
143  return parsed_process;
144}
145
146Expected<TraceIntelPTBundleLoader::ParsedProcess>
147TraceIntelPTBundleLoader::ParseKernel(
148    const JSONTraceBundleDescription &bundle_description) {
149  Expected<ParsedProcess> parsed_process =
150      CreateEmptyProcess(kDefaultKernelProcessID, "");
151
152  if (!parsed_process)
153    return parsed_process.takeError();
154
155  ProcessSP process_sp = parsed_process->target_sp->GetProcessSP();
156
157  // Add cpus as fake threads
158  for (const JSONCpu &cpu : *bundle_description.cpus) {
159    ThreadPostMortemTraceSP thread_sp = std::make_shared<ThreadPostMortemTrace>(
160        *process_sp, static_cast<lldb::tid_t>(cpu.id), FileSpec(cpu.ipt_trace));
161    thread_sp->SetName(formatv("kernel_cpu_{0}", cpu.id).str().c_str());
162    process_sp->GetThreadList().AddThread(thread_sp);
163    parsed_process->threads.push_back(thread_sp);
164  }
165
166  // Add kernel image
167  FileSpec file_spec(bundle_description.kernel->file);
168  ModuleSpec module_spec;
169  module_spec.GetFileSpec() = file_spec;
170
171  Status error;
172  ModuleSP module_sp =
173      parsed_process->target_sp->GetOrCreateModule(module_spec, false, &error);
174
175  if (error.Fail())
176    return error.ToError();
177
178  lldb::addr_t load_address =
179      bundle_description.kernel->load_address
180          ? bundle_description.kernel->load_address->value
181          : kDefaultKernelLoadAddress;
182
183  bool load_addr_changed = false;
184  module_sp->SetLoadAddress(*parsed_process->target_sp, load_address, false,
185                            load_addr_changed);
186
187  process_sp->GetThreadList().SetSelectedThreadByIndexID(0);
188
189  // We invoke DidAttach to create a correct stopped state for the process and
190  // its threads.
191  ArchSpec process_arch;
192  process_sp->DidAttach(process_arch);
193
194  return parsed_process;
195}
196
197Expected<std::vector<TraceIntelPTBundleLoader::ParsedProcess>>
198TraceIntelPTBundleLoader::LoadBundle(
199    const JSONTraceBundleDescription &bundle_description) {
200  std::vector<ParsedProcess> parsed_processes;
201
202  auto HandleError = [&](Error &&err) {
203    // Delete all targets that were created so far in case of failures
204    for (ParsedProcess &parsed_process : parsed_processes)
205      m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp);
206    return std::move(err);
207  };
208
209  if (bundle_description.processes) {
210    for (const JSONProcess &process : *bundle_description.processes) {
211      if (Expected<ParsedProcess> parsed_process = ParseProcess(process))
212        parsed_processes.push_back(std::move(*parsed_process));
213      else
214        return HandleError(parsed_process.takeError());
215    }
216  }
217
218  if (bundle_description.kernel) {
219    if (Expected<ParsedProcess> kernel_process =
220            ParseKernel(bundle_description))
221      parsed_processes.push_back(std::move(*kernel_process));
222    else
223      return HandleError(kernel_process.takeError());
224  }
225
226  return parsed_processes;
227}
228
229StringRef TraceIntelPTBundleLoader::GetSchema() {
230  static std::string schema;
231  if (schema.empty()) {
232    schema = R"({
233  "type": "intel-pt",
234  "cpuInfo": {
235    // CPU information gotten from, for example, /proc/cpuinfo.
236
237    "vendor": "GenuineIntel" | "unknown",
238    "family": integer,
239    "model": integer,
240    "stepping": integer
241  },
242  "processes?": [
243    {
244      "pid": integer,
245      "triple"?: string,
246          // Optional clang/llvm target triple.
247          // This must be provided if the trace will be created not using the
248          // CLI or on a machine other than where the target was traced.
249      "threads": [
250          // A list of known threads for the given process. When context switch
251          // data is provided, LLDB will automatically create threads for the
252          // this process whenever it finds new threads when traversing the
253          // context switches, so passing values to this list in this case is
254          // optional.
255        {
256          "tid": integer,
257          "iptTrace"?: string
258              // Path to the raw Intel PT buffer file for this thread.
259        }
260      ],
261      "modules": [
262        {
263          "systemPath": string,
264              // Original path of the module at runtime.
265          "file"?: string,
266              // Path to a copy of the file if not available at "systemPath".
267          "loadAddress": integer | string decimal | hex string,
268              // Lowest address of the sections of the module loaded on memory.
269          "uuid"?: string,
270              // Build UUID for the file for sanity checks.
271        }
272      ]
273    }
274  ],
275  "cpus"?: [
276    {
277      "id": integer,
278          // Id of this CPU core.
279      "iptTrace": string,
280          // Path to the raw Intel PT buffer for this cpu core.
281      "contextSwitchTrace": string,
282          // Path to the raw perf_event_open context switch trace file for this cpu core.
283          // The perf_event must have been configured with PERF_SAMPLE_TID and
284          // PERF_SAMPLE_TIME, as well as sample_id_all = 1.
285    }
286  ],
287  "tscPerfZeroConversion"?: {
288    // Values used to convert between TSCs and nanoseconds. See the time_zero
289    // section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html
290    // for information.
291
292    "timeMult": integer,
293    "timeShift": integer,
294    "timeZero": integer | string decimal | hex string,
295  },
296  "kernel"?: {
297    "loadAddress"?: integer | string decimal | hex string,
298        // Kernel's image load address. Defaults to 0xffffffff81000000, which
299        // is a load address of x86 architecture if KASLR is not enabled.
300    "file": string,
301        // Path to the kernel image.
302  }
303}
304
305Notes:
306
307- All paths are either absolute or relative to folder containing the bundle
308  description file.
309- "cpus" is provided if and only if processes[].threads[].iptTrace is not provided.
310- "tscPerfZeroConversion" must be provided if "cpus" is provided.
311- If "kernel" is provided, then the "processes" section must be empty or not
312  passed at all, and the "cpus" section must be provided. This configuration
313  indicates that the kernel was traced and user processes weren't. Besides
314  that, the kernel is treated as a single process with one thread per CPU
315  core. This doesn't handle actual kernel threads, but instead treats
316  all the instructions executed by the kernel on each core as an
317  individual thread.})";
318  }
319  return schema;
320}
321
322Error TraceIntelPTBundleLoader::AugmentThreadsFromContextSwitches(
323    JSONTraceBundleDescription &bundle_description) {
324  if (!bundle_description.cpus || !bundle_description.processes)
325    return Error::success();
326
327  if (!bundle_description.tsc_perf_zero_conversion)
328    return createStringError(inconvertibleErrorCode(),
329                             "TSC to nanos conversion values are needed when "
330                             "context switch information is provided.");
331
332  DenseMap<lldb::pid_t, JSONProcess *> indexed_processes;
333  DenseMap<JSONProcess *, DenseSet<tid_t>> indexed_threads;
334
335  for (JSONProcess &process : *bundle_description.processes) {
336    indexed_processes[process.pid] = &process;
337    for (JSONThread &thread : process.threads)
338      indexed_threads[&process].insert(thread.tid);
339  }
340
341  auto on_thread_seen = [&](lldb::pid_t pid, tid_t tid) {
342    auto proc = indexed_processes.find(pid);
343    if (proc == indexed_processes.end())
344      return;
345    if (indexed_threads[proc->second].count(tid))
346      return;
347    indexed_threads[proc->second].insert(tid);
348    proc->second->threads.push_back({tid, /*ipt_trace=*/std::nullopt});
349  };
350
351  for (const JSONCpu &cpu : *bundle_description.cpus) {
352    Error err = Trace::OnDataFileRead(
353        FileSpec(cpu.context_switch_trace),
354        [&](ArrayRef<uint8_t> data) -> Error {
355          Expected<std::vector<ThreadContinuousExecution>> executions =
356              DecodePerfContextSwitchTrace(
357                  data, cpu.id, *bundle_description.tsc_perf_zero_conversion);
358          if (!executions)
359            return executions.takeError();
360          for (const ThreadContinuousExecution &execution : *executions)
361            on_thread_seen(execution.pid, execution.tid);
362          return Error::success();
363        });
364    if (err)
365      return err;
366  }
367  return Error::success();
368}
369
370Expected<TraceSP> TraceIntelPTBundleLoader::CreateTraceIntelPTInstance(
371    JSONTraceBundleDescription &bundle_description,
372    std::vector<ParsedProcess> &parsed_processes) {
373  std::vector<ThreadPostMortemTraceSP> threads;
374  std::vector<ProcessSP> processes;
375  for (const ParsedProcess &parsed_process : parsed_processes) {
376    processes.push_back(parsed_process.target_sp->GetProcessSP());
377    threads.insert(threads.end(), parsed_process.threads.begin(),
378                   parsed_process.threads.end());
379  }
380
381  TraceIntelPT::TraceMode trace_mode = bundle_description.kernel
382                                           ? TraceIntelPT::TraceMode::KernelMode
383                                           : TraceIntelPT::TraceMode::UserMode;
384
385  TraceSP trace_instance = TraceIntelPT::CreateInstanceForPostmortemTrace(
386      bundle_description, processes, threads, trace_mode);
387  for (const ParsedProcess &parsed_process : parsed_processes)
388    parsed_process.target_sp->SetTrace(trace_instance);
389
390  return trace_instance;
391}
392
393void TraceIntelPTBundleLoader::NormalizeAllPaths(
394    JSONTraceBundleDescription &bundle_description) {
395  if (bundle_description.processes) {
396    for (JSONProcess &process : *bundle_description.processes) {
397      for (JSONModule &module : process.modules) {
398        module.system_path = NormalizePath(module.system_path).GetPath();
399        if (module.file)
400          module.file = NormalizePath(*module.file).GetPath();
401      }
402      for (JSONThread &thread : process.threads) {
403        if (thread.ipt_trace)
404          thread.ipt_trace = NormalizePath(*thread.ipt_trace).GetPath();
405      }
406    }
407  }
408  if (bundle_description.cpus) {
409    for (JSONCpu &cpu : *bundle_description.cpus) {
410      cpu.context_switch_trace =
411          NormalizePath(cpu.context_switch_trace).GetPath();
412      cpu.ipt_trace = NormalizePath(cpu.ipt_trace).GetPath();
413    }
414  }
415  if (bundle_description.kernel) {
416    bundle_description.kernel->file =
417        NormalizePath(bundle_description.kernel->file).GetPath();
418  }
419}
420
421Expected<TraceSP> TraceIntelPTBundleLoader::Load() {
422  json::Path::Root root("traceBundle");
423  JSONTraceBundleDescription bundle_description;
424  if (!fromJSON(m_bundle_description, bundle_description, root))
425    return CreateJSONError(root, m_bundle_description);
426
427  NormalizeAllPaths(bundle_description);
428
429  if (Error err = AugmentThreadsFromContextSwitches(bundle_description))
430    return std::move(err);
431
432  if (Expected<std::vector<ParsedProcess>> parsed_processes =
433          LoadBundle(bundle_description))
434    return CreateTraceIntelPTInstance(bundle_description, *parsed_processes);
435  else
436    return parsed_processes.takeError();
437}
438