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 "TraceIntelPTJSONStructs.h"
14 
15 #include "lldb/Core/Debugger.h"
16 #include "lldb/Core/Module.h"
17 #include "lldb/Target/Process.h"
18 #include "lldb/Target/Target.h"
19 
20 using namespace lldb;
21 using namespace lldb_private;
22 using namespace lldb_private::trace_intel_pt;
23 using namespace llvm;
24 
25 FileSpec TraceIntelPTBundleLoader::NormalizePath(const std::string &path) {
26   FileSpec file_spec(path);
27   if (file_spec.IsRelative())
28     file_spec.PrependPathComponent(m_bundle_dir);
29   return file_spec;
30 }
31 
32 Error TraceIntelPTBundleLoader::ParseModule(Target &target,
33                                                  const JSONModule &module) {
34   auto do_parse = [&]() -> Error {
35     FileSpec system_file_spec(module.system_path);
36 
37     FileSpec local_file_spec(module.file.hasValue() ? *module.file
38                                                     : module.system_path);
39 
40     ModuleSpec module_spec;
41     module_spec.GetFileSpec() = local_file_spec;
42     module_spec.GetPlatformFileSpec() = system_file_spec;
43 
44     if (module.uuid.hasValue())
45       module_spec.GetUUID().SetFromStringRef(*module.uuid);
46 
47     Status error;
48     ModuleSP module_sp =
49         target.GetOrCreateModule(module_spec, /*notify*/ false, &error);
50 
51     if (error.Fail())
52       return error.ToError();
53 
54     bool load_addr_changed = false;
55     module_sp->SetLoadAddress(target, module.load_address.value, false,
56                               load_addr_changed);
57     return Error::success();
58   };
59   if (Error err = do_parse())
60     return createStringError(
61         inconvertibleErrorCode(), "Error when parsing module %s. %s",
62         module.system_path.c_str(), toString(std::move(err)).c_str());
63   return Error::success();
64 }
65 
66 Error TraceIntelPTBundleLoader::CreateJSONError(json::Path::Root &root,
67                                                      const json::Value &value) {
68   std::string err;
69   raw_string_ostream os(err);
70   root.printErrorContext(value, os);
71   return createStringError(
72       std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s",
73       toString(root.getError()).c_str(), os.str().c_str(), GetSchema().data());
74 }
75 
76 ThreadPostMortemTraceSP
77 TraceIntelPTBundleLoader::ParseThread(Process &process,
78                                            const JSONThread &thread) {
79   lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid);
80 
81   Optional<FileSpec> trace_file;
82   if (thread.ipt_trace)
83     trace_file = FileSpec(*thread.ipt_trace);
84 
85   ThreadPostMortemTraceSP thread_sp =
86       std::make_shared<ThreadPostMortemTrace>(process, tid, trace_file);
87   process.GetThreadList().AddThread(thread_sp);
88   return thread_sp;
89 }
90 
91 Expected<TraceIntelPTBundleLoader::ParsedProcess>
92 TraceIntelPTBundleLoader::ParseProcess(const JSONProcess &process) {
93   TargetSP target_sp;
94   Status error = m_debugger.GetTargetList().CreateTarget(
95       m_debugger, /*user_exe_path*/ StringRef(), process.triple.value_or(""),
96       eLoadDependentsNo,
97       /*platform_options*/ nullptr, target_sp);
98 
99   if (!target_sp)
100     return error.ToError();
101 
102   ParsedProcess parsed_process;
103   parsed_process.target_sp = target_sp;
104 
105   ProcessSP process_sp = target_sp->CreateProcess(
106       /*listener*/ nullptr, "trace",
107       /*crash_file*/ nullptr,
108       /*can_connect*/ false);
109 
110   process_sp->SetID(static_cast<lldb::pid_t>(process.pid));
111 
112   for (const JSONThread &thread : process.threads)
113     parsed_process.threads.push_back(ParseThread(*process_sp, thread));
114 
115   for (const JSONModule &module : process.modules)
116     if (Error err = ParseModule(*target_sp, module))
117       return std::move(err);
118 
119   if (!process.threads.empty())
120     process_sp->GetThreadList().SetSelectedThreadByIndexID(0);
121 
122   // We invoke DidAttach to create a correct stopped state for the process and
123   // its threads.
124   ArchSpec process_arch;
125   process_sp->DidAttach(process_arch);
126 
127   return parsed_process;
128 }
129 
130 Expected<std::vector<TraceIntelPTBundleLoader::ParsedProcess>>
131 TraceIntelPTBundleLoader::LoadBundle(
132     const JSONTraceBundleDescription &bundle_description) {
133   std::vector<ParsedProcess> parsed_processes;
134 
135   auto HandleError = [&](Error &&err) {
136     // Delete all targets that were created so far in case of failures
137     for (ParsedProcess &parsed_process : parsed_processes)
138       m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp);
139     return std::move(err);
140   };
141 
142   for (const JSONProcess &process : bundle_description.processes) {
143     if (Expected<ParsedProcess> parsed_process = ParseProcess(process))
144       parsed_processes.push_back(std::move(*parsed_process));
145     else
146       return HandleError(parsed_process.takeError());
147   }
148 
149   return parsed_processes;
150 }
151 
152 StringRef TraceIntelPTBundleLoader::GetSchema() {
153   static std::string schema;
154   if (schema.empty()) {
155     schema = R"({
156   "type": "intel-pt",
157   "cpuInfo": {
158     // CPU information gotten from, for example, /proc/cpuinfo.
159 
160     "vendor": "GenuineIntel" | "unknown",
161     "family": integer,
162     "model": integer,
163     "stepping": integer
164   },
165   "processes": [
166     {
167       "pid": integer,
168       "triple"?: string,
169           // Optional clang/llvm target triple.
170       "threads": [
171           // A list of known threads for the given process. When context switch
172           // data is provided, LLDB will automatically create threads for the
173           // this process whenever it finds new threads when traversing the
174           // context switches, so passing values to this list in this case is
175           // optional.
176         {
177           "tid": integer,
178           "iptTrace"?: string
179               // Path to the raw Intel PT buffer file for this thread.
180         }
181       ],
182       "modules": [
183         {
184           "systemPath": string,
185               // Original path of the module at runtime.
186           "file"?: string,
187               // Path to a copy of the file if not available at "systemPath".
188           "loadAddress": integer | string decimal | hex string,
189               // Lowest address of the sections of the module loaded on memory.
190           "uuid"?: string,
191               // Build UUID for the file for sanity checks.
192         }
193       ]
194     }
195   ],
196   "cpus"?: [
197     {
198       "id": integer,
199           // Id of this CPU core.
200       "iptTrace": string,
201           // Path to the raw Intel PT buffer for this cpu core.
202       "contextSwitchTrace": string,
203           // Path to the raw perf_event_open context switch trace file for this cpu core.
204           // The perf_event must have been configured with PERF_SAMPLE_TID and
205           // PERF_SAMPLE_TIME, as well as sample_id_all = 1.
206     }
207   ],
208   "tscPerfZeroConversion"?: {
209     // Values used to convert between TSCs and nanoseconds. See the time_zero
210     // section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html
211     // for for information.
212 
213     "timeMult": integer,
214     "timeShift": integer,
215     "timeZero": integer | string decimal | hex string,
216   }
217 }
218 
219 Notes:
220 
221 - All paths are either absolute or relative to folder containing the bundle description file.
222 - "cpus" is provided if and only if processes[].threads[].iptTrace is not provided.
223 - "tscPerfZeroConversion" must be provided if "cpus" is provided.
224  })";
225   }
226   return schema;
227 }
228 
229 Error TraceIntelPTBundleLoader::AugmentThreadsFromContextSwitches(
230     JSONTraceBundleDescription &bundle_description) {
231   if (!bundle_description.cpus)
232     return Error::success();
233 
234   if (!bundle_description.tsc_perf_zero_conversion)
235     return createStringError(inconvertibleErrorCode(),
236                              "TSC to nanos conversion values are needed when "
237                              "context switch information is provided.");
238 
239   DenseMap<lldb::pid_t, JSONProcess *> indexed_processes;
240   DenseMap<JSONProcess *, DenseSet<tid_t>> indexed_threads;
241 
242   for (JSONProcess &process : bundle_description.processes) {
243     indexed_processes[process.pid] = &process;
244     for (JSONThread &thread : process.threads)
245       indexed_threads[&process].insert(thread.tid);
246   }
247 
248   auto on_thread_seen = [&](lldb::pid_t pid, tid_t tid) {
249     auto proc = indexed_processes.find(pid);
250     if (proc == indexed_processes.end())
251       return;
252     if (indexed_threads[proc->second].count(tid))
253       return;
254     indexed_threads[proc->second].insert(tid);
255     proc->second->threads.push_back({tid, /*ipt_trace=*/None});
256   };
257 
258   for (const JSONCpu &cpu : *bundle_description.cpus) {
259     Error err = Trace::OnDataFileRead(
260         FileSpec(cpu.context_switch_trace),
261         [&](ArrayRef<uint8_t> data) -> Error {
262           Expected<std::vector<ThreadContinuousExecution>> executions =
263               DecodePerfContextSwitchTrace(data, cpu.id,
264                                            *bundle_description.tsc_perf_zero_conversion);
265           if (!executions)
266             return executions.takeError();
267           for (const ThreadContinuousExecution &execution : *executions)
268             on_thread_seen(execution.pid, execution.tid);
269           return Error::success();
270         });
271     if (err)
272       return err;
273   }
274   return Error::success();
275 }
276 
277 Expected<TraceSP> TraceIntelPTBundleLoader::CreateTraceIntelPTInstance(
278     JSONTraceBundleDescription &bundle_description, std::vector<ParsedProcess> &parsed_processes) {
279   std::vector<ThreadPostMortemTraceSP> threads;
280   std::vector<ProcessSP> processes;
281   for (const ParsedProcess &parsed_process : parsed_processes) {
282     processes.push_back(parsed_process.target_sp->GetProcessSP());
283     threads.insert(threads.end(), parsed_process.threads.begin(),
284                    parsed_process.threads.end());
285   }
286 
287   TraceSP trace_instance = TraceIntelPT::CreateInstanceForPostmortemTrace(
288       bundle_description, processes, threads);
289   for (const ParsedProcess &parsed_process : parsed_processes)
290     parsed_process.target_sp->SetTrace(trace_instance);
291 
292   return trace_instance;
293 }
294 
295 void TraceIntelPTBundleLoader::NormalizeAllPaths(
296     JSONTraceBundleDescription &bundle_description) {
297   for (JSONProcess &process : bundle_description.processes) {
298     for (JSONModule &module : process.modules) {
299       module.system_path = NormalizePath(module.system_path).GetPath();
300       if (module.file)
301         module.file = NormalizePath(*module.file).GetPath();
302     }
303     for (JSONThread &thread : process.threads) {
304       if (thread.ipt_trace)
305         thread.ipt_trace = NormalizePath(*thread.ipt_trace).GetPath();
306     }
307   }
308   if (bundle_description.cpus) {
309     for (JSONCpu &cpu : *bundle_description.cpus) {
310       cpu.context_switch_trace =
311           NormalizePath(cpu.context_switch_trace).GetPath();
312       cpu.ipt_trace = NormalizePath(cpu.ipt_trace).GetPath();
313     }
314   }
315 }
316 
317 Expected<TraceSP> TraceIntelPTBundleLoader::Load() {
318   json::Path::Root root("traceBundle");
319   JSONTraceBundleDescription bundle_description;
320   if (!fromJSON(m_bundle_description, bundle_description, root))
321     return CreateJSONError(root, m_bundle_description);
322 
323   NormalizeAllPaths(bundle_description);
324 
325   if (Error err = AugmentThreadsFromContextSwitches(bundle_description))
326     return std::move(err);
327 
328   if (Expected<std::vector<ParsedProcess>> parsed_processes =
329           LoadBundle(bundle_description))
330     return CreateTraceIntelPTInstance(bundle_description, *parsed_processes);
331   else
332     return parsed_processes.takeError();
333 }
334