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
NormalizePath(const std::string & path)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
ParseModule(Target & target,const JSONModule & module)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
CreateJSONError(json::Path::Root & root,const json::Value & value)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
ParseThread(Process & process,const JSONThread & thread)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>
ParseProcess(const JSONProcess & process)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>>
LoadBundle(const JSONTraceBundleDescription & bundle_description)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
GetSchema()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
AugmentThreadsFromContextSwitches(JSONTraceBundleDescription & bundle_description)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
CreateTraceIntelPTInstance(JSONTraceBundleDescription & bundle_description,std::vector<ParsedProcess> & parsed_processes)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
NormalizeAllPaths(JSONTraceBundleDescription & bundle_description)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
Load()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