1 //===-- TraceIntelPTBundleSaver.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 "TraceIntelPTBundleSaver.h"
10 #include "PerfContextSwitchDecoder.h"
11 #include "TraceIntelPT.h"
12 #include "TraceIntelPTConstants.h"
13 #include "TraceIntelPTJSONStructs.h"
14 #include "lldb/Core/Module.h"
15 #include "lldb/Core/ModuleList.h"
16 #include "lldb/Target/Process.h"
17 #include "lldb/Target/SectionLoadList.h"
18 #include "lldb/Target/Target.h"
19 #include "lldb/Target/ThreadList.h"
20 #include "lldb/lldb-types.h"
21 #include "llvm/Support/Error.h"
22 #include "llvm/Support/JSON.h"
30 using namespace lldb_private
;
31 using namespace lldb_private::trace_intel_pt
;
34 /// Strip the \p directory component from the given \p path. It assumes that \p
35 /// directory is a prefix of \p path.
36 static std::string
GetRelativePath(const FileSpec
&directory
,
37 const FileSpec
&path
) {
38 return path
.GetPath().substr(directory
.GetPath().size() + 1);
41 /// Write a stream of bytes from \p data to the given output file.
42 /// It creates or overwrites the output file, but not append.
43 static llvm::Error
WriteBytesToDisk(FileSpec
&output_file
,
44 ArrayRef
<uint8_t> data
) {
45 std::basic_fstream
<char> out_fs
= std::fstream(
46 output_file
.GetPath().c_str(), std::ios::out
| std::ios::binary
);
48 out_fs
.write(reinterpret_cast<const char *>(&data
[0]), data
.size());
52 return createStringError(inconvertibleErrorCode(),
53 formatv("couldn't write to the file {0}",
54 output_file
.GetPath().c_str()));
55 return Error::success();
58 /// Save the trace bundle description JSON object inside the given directory
59 /// as a file named \a trace.json.
61 /// \param[in] trace_bundle_description
62 /// The trace bundle description as JSON Object.
64 /// \param[in] directory
65 /// The directory where the JSON file will be saved.
68 /// A \a FileSpec pointing to the bundle description file, or an \a
69 /// llvm::Error otherwise.
70 static Expected
<FileSpec
>
71 SaveTraceBundleDescription(const llvm::json::Value
&trace_bundle_description
,
72 const FileSpec
&directory
) {
73 FileSpec trace_path
= directory
;
74 trace_path
.AppendPathComponent("trace.json");
75 std::ofstream
os(trace_path
.GetPath());
76 os
<< formatv("{0:2}", trace_bundle_description
).str();
79 return createStringError(inconvertibleErrorCode(),
80 formatv("couldn't write to the file {0}",
81 trace_path
.GetPath().c_str()));
85 /// Build the threads sub-section of the trace bundle description file.
86 /// Any associated binary files are created inside the given directory.
88 /// \param[in] process
89 /// The process being traced.
91 /// \param[in] directory
92 /// The directory where files will be saved when building the threads
96 /// The threads section or \a llvm::Error in case of failures.
97 static llvm::Expected
<std::vector
<JSONThread
>>
98 BuildThreadsSection(Process
&process
, FileSpec directory
) {
99 std::vector
<JSONThread
> json_threads
;
100 TraceSP trace_sp
= process
.GetTarget().GetTrace();
102 FileSpec threads_dir
= directory
;
103 threads_dir
.AppendPathComponent("threads");
104 sys::fs::create_directories(threads_dir
.GetPath().c_str());
106 for (ThreadSP thread_sp
: process
.Threads()) {
107 lldb::tid_t tid
= thread_sp
->GetID();
108 if (!trace_sp
->IsTraced(tid
))
111 JSONThread json_thread
;
112 json_thread
.tid
= tid
;
114 if (trace_sp
->GetTracedCpus().empty()) {
115 FileSpec output_file
= threads_dir
;
116 output_file
.AppendPathComponent(std::to_string(tid
) + ".intelpt_trace");
117 json_thread
.ipt_trace
= GetRelativePath(directory
, output_file
);
119 llvm::Error err
= process
.GetTarget().GetTrace()->OnThreadBinaryDataRead(
120 tid
, IntelPTDataKinds::kIptTrace
,
121 [&](llvm::ArrayRef
<uint8_t> data
) -> llvm::Error
{
122 return WriteBytesToDisk(output_file
, data
);
125 return std::move(err
);
128 json_threads
.push_back(std::move(json_thread
));
134 /// an \a llvm::Error in case of failures, \a std::nullopt if the trace is not
135 /// written to disk because the trace is empty and the \p compact flag is
136 /// present, or the FileSpec of the trace file on disk.
137 static Expected
<std::optional
<FileSpec
>>
138 WriteContextSwitchTrace(TraceIntelPT
&trace_ipt
, lldb::cpu_id_t cpu_id
,
139 const FileSpec
&cpus_dir
, bool compact
) {
140 FileSpec output_context_switch_trace
= cpus_dir
;
141 output_context_switch_trace
.AppendPathComponent(std::to_string(cpu_id
) +
142 ".perf_context_switch_trace");
144 bool should_skip
= false;
146 Error err
= trace_ipt
.OnCpuBinaryDataRead(
147 cpu_id
, IntelPTDataKinds::kPerfContextSwitchTrace
,
148 [&](llvm::ArrayRef
<uint8_t> data
) -> llvm::Error
{
150 return WriteBytesToDisk(output_context_switch_trace
, data
);
152 std::set
<lldb::pid_t
> pids
;
153 for (Process
*process
: trace_ipt
.GetAllProcesses())
154 pids
.insert(process
->GetID());
156 Expected
<std::vector
<uint8_t>> compact_context_switch_trace
=
157 FilterProcessesFromContextSwitchTrace(data
, pids
);
158 if (!compact_context_switch_trace
)
159 return compact_context_switch_trace
.takeError();
161 if (compact_context_switch_trace
->empty()) {
163 return Error::success();
166 return WriteBytesToDisk(output_context_switch_trace
,
167 *compact_context_switch_trace
);
170 return std::move(err
);
174 return output_context_switch_trace
;
177 static Expected
<FileSpec
> WriteIntelPTTrace(TraceIntelPT
&trace_ipt
,
178 lldb::cpu_id_t cpu_id
,
179 const FileSpec
&cpus_dir
) {
180 FileSpec output_trace
= cpus_dir
;
181 output_trace
.AppendPathComponent(std::to_string(cpu_id
) + ".intelpt_trace");
183 Error err
= trace_ipt
.OnCpuBinaryDataRead(
184 cpu_id
, IntelPTDataKinds::kIptTrace
,
185 [&](llvm::ArrayRef
<uint8_t> data
) -> llvm::Error
{
186 return WriteBytesToDisk(output_trace
, data
);
189 return std::move(err
);
193 static llvm::Expected
<std::optional
<std::vector
<JSONCpu
>>>
194 BuildCpusSection(TraceIntelPT
&trace_ipt
, FileSpec directory
, bool compact
) {
195 if (trace_ipt
.GetTracedCpus().empty())
198 std::vector
<JSONCpu
> json_cpus
;
199 FileSpec cpus_dir
= directory
;
200 cpus_dir
.AppendPathComponent("cpus");
201 sys::fs::create_directories(cpus_dir
.GetPath().c_str());
203 for (lldb::cpu_id_t cpu_id
: trace_ipt
.GetTracedCpus()) {
205 json_cpu
.id
= cpu_id
;
206 Expected
<std::optional
<FileSpec
>> context_switch_trace_path
=
207 WriteContextSwitchTrace(trace_ipt
, cpu_id
, cpus_dir
, compact
);
208 if (!context_switch_trace_path
)
209 return context_switch_trace_path
.takeError();
210 if (!*context_switch_trace_path
)
212 json_cpu
.context_switch_trace
=
213 GetRelativePath(directory
, **context_switch_trace_path
);
215 if (Expected
<FileSpec
> ipt_trace_path
=
216 WriteIntelPTTrace(trace_ipt
, cpu_id
, cpus_dir
))
217 json_cpu
.ipt_trace
= GetRelativePath(directory
, *ipt_trace_path
);
219 return ipt_trace_path
.takeError();
221 json_cpus
.push_back(std::move(json_cpu
));
226 /// Build modules sub-section of the trace bundle. The original modules
227 /// will be copied over to the \a <directory/modules> folder. Invalid modules
229 /// Copying the modules has the benefit of making these
230 /// directories self-contained, as the raw traces and modules are part of the
231 /// output directory and can be sent to another machine, where lldb can load
232 /// them and replicate exactly the same trace session.
234 /// \param[in] process
235 /// The process being traced.
237 /// \param[in] directory
238 /// The directory where the modules files will be saved when building
239 /// the modules section.
240 /// Example: If a module \a libbar.so exists in the path
241 /// \a /usr/lib/foo/libbar.so, then it will be copied to
242 /// \a <directory>/modules/usr/lib/foo/libbar.so.
245 /// The modules section or \a llvm::Error in case of failures.
246 static llvm::Expected
<std::vector
<JSONModule
>>
247 BuildModulesSection(Process
&process
, FileSpec directory
) {
248 std::vector
<JSONModule
> json_modules
;
249 ModuleList module_list
= process
.GetTarget().GetImages();
250 for (size_t i
= 0; i
< module_list
.GetSize(); ++i
) {
251 ModuleSP
module_sp(module_list
.GetModuleAtIndex(i
));
254 std::string system_path
= module_sp
->GetPlatformFileSpec().GetPath();
255 // TODO: support memory-only libraries like [vdso]
256 if (!module_sp
->GetFileSpec().IsAbsolute())
259 std::string file
= module_sp
->GetFileSpec().GetPath();
260 ObjectFile
*objfile
= module_sp
->GetObjectFile();
261 if (objfile
== nullptr)
264 lldb::addr_t load_addr
= LLDB_INVALID_ADDRESS
;
265 Address
base_addr(objfile
->GetBaseAddress());
266 if (base_addr
.IsValid() &&
267 !process
.GetTarget().GetSectionLoadList().IsEmpty())
268 load_addr
= base_addr
.GetLoadAddress(&process
.GetTarget());
270 if (load_addr
== LLDB_INVALID_ADDRESS
)
273 FileSpec path_to_copy_module
= directory
;
274 path_to_copy_module
.AppendPathComponent("modules");
275 path_to_copy_module
.AppendPathComponent(system_path
);
276 sys::fs::create_directories(path_to_copy_module
.GetDirectory().AsCString());
278 if (std::error_code ec
=
279 llvm::sys::fs::copy_file(file
, path_to_copy_module
.GetPath()))
280 return createStringError(
281 inconvertibleErrorCode(),
282 formatv("couldn't write to the file. {0}", ec
.message()));
284 json_modules
.push_back(
285 JSONModule
{system_path
, GetRelativePath(directory
, path_to_copy_module
),
286 JSONUINT64
{load_addr
}, module_sp
->GetUUID().GetAsString()});
291 /// Build the processes section of the trace bundle description object. Besides
292 /// returning the processes information, this method saves to disk all modules
293 /// and raw traces corresponding to the traced threads of the given process.
295 /// \param[in] process
296 /// The process being traced.
298 /// \param[in] directory
299 /// The directory where files will be saved when building the processes
303 /// The processes section or \a llvm::Error in case of failures.
304 static llvm::Expected
<JSONProcess
>
305 BuildProcessSection(Process
&process
, const FileSpec
&directory
) {
306 Expected
<std::vector
<JSONThread
>> json_threads
=
307 BuildThreadsSection(process
, directory
);
309 return json_threads
.takeError();
311 Expected
<std::vector
<JSONModule
>> json_modules
=
312 BuildModulesSection(process
, directory
);
314 return json_modules
.takeError();
318 process
.GetTarget().GetArchitecture().GetTriple().getTriple(),
319 json_threads
.get(), json_modules
.get()};
322 /// See BuildProcessSection()
323 static llvm::Expected
<std::vector
<JSONProcess
>>
324 BuildProcessesSection(TraceIntelPT
&trace_ipt
, const FileSpec
&directory
) {
325 std::vector
<JSONProcess
> processes
;
326 for (Process
*process
: trace_ipt
.GetAllProcesses()) {
327 if (llvm::Expected
<JSONProcess
> json_process
=
328 BuildProcessSection(*process
, directory
))
329 processes
.push_back(std::move(*json_process
));
331 return json_process
.takeError();
336 static llvm::Expected
<JSONKernel
>
337 BuildKernelSection(TraceIntelPT
&trace_ipt
, const FileSpec
&directory
) {
338 JSONKernel json_kernel
;
339 std::vector
<Process
*> processes
= trace_ipt
.GetAllProcesses();
340 Process
*kernel_process
= processes
[0];
342 assert(processes
.size() == 1 && "User processeses exist in kernel mode");
343 assert(kernel_process
->GetID() == kDefaultKernelProcessID
&&
344 "Kernel process not exist");
346 Expected
<std::vector
<JSONModule
>> json_modules
=
347 BuildModulesSection(*kernel_process
, directory
);
349 return json_modules
.takeError();
351 JSONModule kernel_image
= json_modules
.get()[0];
352 return JSONKernel
{kernel_image
.load_address
, kernel_image
.system_path
};
355 Expected
<FileSpec
> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT
&trace_ipt
,
358 if (std::error_code ec
=
359 sys::fs::create_directories(directory
.GetPath().c_str()))
360 return llvm::errorCodeToError(ec
);
362 Expected
<pt_cpu
> cpu_info
= trace_ipt
.GetCPUInfo();
364 return cpu_info
.takeError();
366 FileSystem::Instance().Resolve(directory
);
368 Expected
<std::optional
<std::vector
<JSONCpu
>>> json_cpus
=
369 BuildCpusSection(trace_ipt
, directory
, compact
);
371 return json_cpus
.takeError();
373 std::optional
<std::vector
<JSONProcess
>> json_processes
;
374 std::optional
<JSONKernel
> json_kernel
;
376 if (trace_ipt
.GetTraceMode() == TraceIntelPT::TraceMode::KernelMode
) {
377 Expected
<std::optional
<JSONKernel
>> exp_json_kernel
=
378 BuildKernelSection(trace_ipt
, directory
);
379 if (!exp_json_kernel
)
380 return exp_json_kernel
.takeError();
382 json_kernel
= *exp_json_kernel
;
384 Expected
<std::optional
<std::vector
<JSONProcess
>>> exp_json_processes
=
385 BuildProcessesSection(trace_ipt
, directory
);
386 if (!exp_json_processes
)
387 return exp_json_processes
.takeError();
389 json_processes
= *exp_json_processes
;
392 JSONTraceBundleDescription json_intel_pt_bundle_desc
{
397 trace_ipt
.GetPerfZeroTscConversion(),
400 return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc
),