1 //===-- TraceIntelPTBundleLoader.cpp --------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "TraceIntelPTBundleLoader.h"
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/Target.h"
22 using namespace lldb_private
;
23 using namespace lldb_private::trace_intel_pt
;
26 FileSpec
TraceIntelPTBundleLoader::NormalizePath(const std::string
&path
) {
27 FileSpec
file_spec(path
);
28 if (file_spec
.IsRelative())
29 file_spec
.PrependPathComponent(m_bundle_dir
);
33 Error
TraceIntelPTBundleLoader::ParseModule(Target
&target
,
34 const JSONModule
&module
) {
35 auto do_parse
= [&]() -> Error
{
36 FileSpec
system_file_spec(module
.system_path
);
38 FileSpec
local_file_spec(module
.file
.has_value() ? *module
.file
39 : module
.system_path
);
41 ModuleSpec module_spec
;
42 module_spec
.GetFileSpec() = local_file_spec
;
43 module_spec
.GetPlatformFileSpec() = system_file_spec
;
45 if (module
.uuid
.has_value())
46 module_spec
.GetUUID().SetFromStringRef(*module
.uuid
);
50 target
.GetOrCreateModule(module_spec
, /*notify*/ false, &error
);
53 return error
.ToError();
55 bool load_addr_changed
= false;
56 module_sp
->SetLoadAddress(target
, module
.load_address
.value
, false,
58 return Error::success();
60 if (Error err
= do_parse())
61 return createStringError(
62 inconvertibleErrorCode(), "Error when parsing module %s. %s",
63 module
.system_path
.c_str(), toString(std::move(err
)).c_str());
64 return Error::success();
67 Error
TraceIntelPTBundleLoader::CreateJSONError(json::Path::Root
&root
,
68 const json::Value
&value
) {
70 raw_string_ostream
os(err
);
71 root
.printErrorContext(value
, os
);
72 return createStringError(
73 std::errc::invalid_argument
, "%s\n\nContext:\n%s\n\nSchema:\n%s",
74 toString(root
.getError()).c_str(), os
.str().c_str(), GetSchema().data());
77 ThreadPostMortemTraceSP
78 TraceIntelPTBundleLoader::ParseThread(Process
&process
,
79 const JSONThread
&thread
) {
80 lldb::tid_t tid
= static_cast<lldb::tid_t
>(thread
.tid
);
82 std::optional
<FileSpec
> trace_file
;
84 trace_file
= FileSpec(*thread
.ipt_trace
);
86 ThreadPostMortemTraceSP thread_sp
=
87 std::make_shared
<ThreadPostMortemTrace
>(process
, tid
, trace_file
);
88 process
.GetThreadList().AddThread(thread_sp
);
92 Expected
<TraceIntelPTBundleLoader::ParsedProcess
>
93 TraceIntelPTBundleLoader::CreateEmptyProcess(lldb::pid_t pid
,
94 llvm::StringRef triple
) {
96 Status error
= m_debugger
.GetTargetList().CreateTarget(
97 m_debugger
, /*user_exe_path*/ StringRef(), triple
, eLoadDependentsNo
,
98 /*platform_options*/ nullptr, target_sp
);
101 return error
.ToError();
103 ParsedProcess parsed_process
;
104 parsed_process
.target_sp
= target_sp
;
106 ProcessSP process_sp
= target_sp
->CreateProcess(
107 /*listener*/ nullptr, "trace",
108 /*crash_file*/ nullptr,
109 /*can_connect*/ false);
111 process_sp
->SetID(static_cast<lldb::pid_t
>(pid
));
113 return parsed_process
;
116 Expected
<TraceIntelPTBundleLoader::ParsedProcess
>
117 TraceIntelPTBundleLoader::ParseProcess(const JSONProcess
&process
) {
118 Expected
<ParsedProcess
> parsed_process
=
119 CreateEmptyProcess(process
.pid
, process
.triple
.value_or(""));
122 return parsed_process
.takeError();
124 ProcessSP process_sp
= parsed_process
->target_sp
->GetProcessSP();
126 for (const JSONThread
&thread
: process
.threads
)
127 parsed_process
->threads
.push_back(ParseThread(*process_sp
, thread
));
129 for (const JSONModule
&module
: process
.modules
)
130 if (Error err
= ParseModule(*parsed_process
->target_sp
, module
))
131 return std::move(err
);
133 if (!process
.threads
.empty())
134 process_sp
->GetThreadList().SetSelectedThreadByIndexID(0);
136 // We invoke DidAttach to create a correct stopped state for the process and
138 ArchSpec process_arch
;
139 process_sp
->DidAttach(process_arch
);
141 return parsed_process
;
144 Expected
<TraceIntelPTBundleLoader::ParsedProcess
>
145 TraceIntelPTBundleLoader::ParseKernel(
146 const JSONTraceBundleDescription
&bundle_description
) {
147 Expected
<ParsedProcess
> parsed_process
=
148 CreateEmptyProcess(kDefaultKernelProcessID
, "");
151 return parsed_process
.takeError();
153 ProcessSP process_sp
= parsed_process
->target_sp
->GetProcessSP();
155 // Add cpus as fake threads
156 for (const JSONCpu
&cpu
: *bundle_description
.cpus
) {
157 ThreadPostMortemTraceSP thread_sp
= std::make_shared
<ThreadPostMortemTrace
>(
158 *process_sp
, static_cast<lldb::tid_t
>(cpu
.id
), FileSpec(cpu
.ipt_trace
));
159 thread_sp
->SetName(formatv("kernel_cpu_{0}", cpu
.id
).str().c_str());
160 process_sp
->GetThreadList().AddThread(thread_sp
);
161 parsed_process
->threads
.push_back(thread_sp
);
165 FileSpec
file_spec(bundle_description
.kernel
->file
);
166 ModuleSpec module_spec
;
167 module_spec
.GetFileSpec() = file_spec
;
171 parsed_process
->target_sp
->GetOrCreateModule(module_spec
, false, &error
);
174 return error
.ToError();
176 lldb::addr_t load_address
=
177 bundle_description
.kernel
->load_address
178 ? bundle_description
.kernel
->load_address
->value
179 : kDefaultKernelLoadAddress
;
181 bool load_addr_changed
= false;
182 module_sp
->SetLoadAddress(*parsed_process
->target_sp
, load_address
, false,
185 process_sp
->GetThreadList().SetSelectedThreadByIndexID(0);
187 // We invoke DidAttach to create a correct stopped state for the process and
189 ArchSpec process_arch
;
190 process_sp
->DidAttach(process_arch
);
192 return parsed_process
;
195 Expected
<std::vector
<TraceIntelPTBundleLoader::ParsedProcess
>>
196 TraceIntelPTBundleLoader::LoadBundle(
197 const JSONTraceBundleDescription
&bundle_description
) {
198 std::vector
<ParsedProcess
> parsed_processes
;
200 auto HandleError
= [&](Error
&&err
) {
201 // Delete all targets that were created so far in case of failures
202 for (ParsedProcess
&parsed_process
: parsed_processes
)
203 m_debugger
.GetTargetList().DeleteTarget(parsed_process
.target_sp
);
204 return std::move(err
);
207 if (bundle_description
.processes
) {
208 for (const JSONProcess
&process
: *bundle_description
.processes
) {
209 if (Expected
<ParsedProcess
> parsed_process
= ParseProcess(process
))
210 parsed_processes
.push_back(std::move(*parsed_process
));
212 return HandleError(parsed_process
.takeError());
216 if (bundle_description
.kernel
) {
217 if (Expected
<ParsedProcess
> kernel_process
=
218 ParseKernel(bundle_description
))
219 parsed_processes
.push_back(std::move(*kernel_process
));
221 return HandleError(kernel_process
.takeError());
224 return parsed_processes
;
227 StringRef
TraceIntelPTBundleLoader::GetSchema() {
228 static std::string schema
;
229 if (schema
.empty()) {
233 // CPU information gotten from, for example, /proc/cpuinfo.
235 "vendor
": "GenuineIntel
" | "unknown
",
244 // Optional clang/llvm target triple.
245 // This must be provided if the trace will be created not using the
246 // CLI or on a machine other than where the target was traced.
248 // A list of known threads for the given process. When context switch
249 // data is provided, LLDB will automatically create threads for the
250 // this process whenever it finds new threads when traversing the
251 // context switches, so passing values to this list in this case is
256 // Path to the raw Intel PT buffer file for this thread.
261 "systemPath
": string,
262 // Original path of the module at runtime.
264 // Path to a copy of the file if not available at "systemPath
".
265 "loadAddress
": integer | string decimal | hex string,
266 // Lowest address of the sections of the module loaded on memory.
268 // Build UUID for the file for sanity checks.
276 // Id of this CPU core.
278 // Path to the raw Intel PT buffer for this cpu core.
279 "contextSwitchTrace
": string,
280 // Path to the raw perf_event_open context switch trace file for this cpu core.
281 // The perf_event must have been configured with PERF_SAMPLE_TID and
282 // PERF_SAMPLE_TIME, as well as sample_id_all = 1.
285 "tscPerfZeroConversion
"?: {
286 // Values used to convert between TSCs and nanoseconds. See the time_zero
287 // section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html
291 "timeShift
": integer,
292 "timeZero
": integer | string decimal | hex string,
295 "loadAddress
"?: integer | string decimal | hex string,
296 // Kernel's image load address. Defaults to 0xffffffff81000000, which
297 // is a load address of x86 architecture if KASLR is not enabled.
299 // Path to the kernel image.
305 - All paths are either absolute or relative to folder containing the bundle
307 - "cpus
" is provided if and only if processes[].threads[].iptTrace is not provided.
308 - "tscPerfZeroConversion
" must be provided if "cpus
" is provided.
309 - If "kernel
" is provided, then the "processes
" section must be empty or not
310 passed at all, and the "cpus
" section must be provided. This configuration
311 indicates that the kernel was traced and user processes weren't. Besides
312 that, the kernel is treated as a single process with one thread per CPU
313 core. This doesn't handle actual kernel threads, but instead treats
314 all the instructions executed by the kernel on each core as an
315 individual thread.})";
320 Error
TraceIntelPTBundleLoader::AugmentThreadsFromContextSwitches(
321 JSONTraceBundleDescription
&bundle_description
) {
322 if (!bundle_description
.cpus
|| !bundle_description
.processes
)
323 return Error::success();
325 if (!bundle_description
.tsc_perf_zero_conversion
)
326 return createStringError(inconvertibleErrorCode(),
327 "TSC to nanos conversion values are needed when "
328 "context switch information is provided.");
330 DenseMap
<lldb::pid_t
, JSONProcess
*> indexed_processes
;
331 DenseMap
<JSONProcess
*, DenseSet
<tid_t
>> indexed_threads
;
333 for (JSONProcess
&process
: *bundle_description
.processes
) {
334 indexed_processes
[process
.pid
] = &process
;
335 for (JSONThread
&thread
: process
.threads
)
336 indexed_threads
[&process
].insert(thread
.tid
);
339 auto on_thread_seen
= [&](lldb::pid_t pid
, tid_t tid
) {
340 auto proc
= indexed_processes
.find(pid
);
341 if (proc
== indexed_processes
.end())
343 if (indexed_threads
[proc
->second
].count(tid
))
345 indexed_threads
[proc
->second
].insert(tid
);
346 proc
->second
->threads
.push_back({tid
, /*ipt_trace=*/None
});
349 for (const JSONCpu
&cpu
: *bundle_description
.cpus
) {
350 Error err
= Trace::OnDataFileRead(
351 FileSpec(cpu
.context_switch_trace
),
352 [&](ArrayRef
<uint8_t> data
) -> Error
{
353 Expected
<std::vector
<ThreadContinuousExecution
>> executions
=
354 DecodePerfContextSwitchTrace(
355 data
, cpu
.id
, *bundle_description
.tsc_perf_zero_conversion
);
357 return executions
.takeError();
358 for (const ThreadContinuousExecution
&execution
: *executions
)
359 on_thread_seen(execution
.pid
, execution
.tid
);
360 return Error::success();
365 return Error::success();
368 Expected
<TraceSP
> TraceIntelPTBundleLoader::CreateTraceIntelPTInstance(
369 JSONTraceBundleDescription
&bundle_description
,
370 std::vector
<ParsedProcess
> &parsed_processes
) {
371 std::vector
<ThreadPostMortemTraceSP
> threads
;
372 std::vector
<ProcessSP
> processes
;
373 for (const ParsedProcess
&parsed_process
: parsed_processes
) {
374 processes
.push_back(parsed_process
.target_sp
->GetProcessSP());
375 threads
.insert(threads
.end(), parsed_process
.threads
.begin(),
376 parsed_process
.threads
.end());
379 TraceIntelPT::TraceMode trace_mode
= bundle_description
.kernel
380 ? TraceIntelPT::TraceMode::KernelMode
381 : TraceIntelPT::TraceMode::UserMode
;
383 TraceSP trace_instance
= TraceIntelPT::CreateInstanceForPostmortemTrace(
384 bundle_description
, processes
, threads
, trace_mode
);
385 for (const ParsedProcess
&parsed_process
: parsed_processes
)
386 parsed_process
.target_sp
->SetTrace(trace_instance
);
388 return trace_instance
;
391 void TraceIntelPTBundleLoader::NormalizeAllPaths(
392 JSONTraceBundleDescription
&bundle_description
) {
393 if (bundle_description
.processes
) {
394 for (JSONProcess
&process
: *bundle_description
.processes
) {
395 for (JSONModule
&module
: process
.modules
) {
396 module
.system_path
= NormalizePath(module
.system_path
).GetPath();
398 module
.file
= NormalizePath(*module
.file
).GetPath();
400 for (JSONThread
&thread
: process
.threads
) {
401 if (thread
.ipt_trace
)
402 thread
.ipt_trace
= NormalizePath(*thread
.ipt_trace
).GetPath();
406 if (bundle_description
.cpus
) {
407 for (JSONCpu
&cpu
: *bundle_description
.cpus
) {
408 cpu
.context_switch_trace
=
409 NormalizePath(cpu
.context_switch_trace
).GetPath();
410 cpu
.ipt_trace
= NormalizePath(cpu
.ipt_trace
).GetPath();
413 if (bundle_description
.kernel
) {
414 bundle_description
.kernel
->file
=
415 NormalizePath(bundle_description
.kernel
->file
).GetPath();
419 Expected
<TraceSP
> TraceIntelPTBundleLoader::Load() {
420 json::Path::Root
root("traceBundle");
421 JSONTraceBundleDescription bundle_description
;
422 if (!fromJSON(m_bundle_description
, bundle_description
, root
))
423 return CreateJSONError(root
, m_bundle_description
);
425 NormalizeAllPaths(bundle_description
);
427 if (Error err
= AugmentThreadsFromContextSwitches(bundle_description
))
428 return std::move(err
);
430 if (Expected
<std::vector
<ParsedProcess
>> parsed_processes
=
431 LoadBundle(bundle_description
))
432 return CreateTraceIntelPTInstance(bundle_description
, *parsed_processes
);
434 return parsed_processes
.takeError();