LLDB mainline
TraceIntelPTBundleSaver.cpp
Go to the documentation of this file.
1//===-- TraceIntelPTBundleSaver.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
11#include "TraceIntelPT.h"
14#include "lldb/Core/Module.h"
16#include "lldb/Target/Process.h"
18#include "lldb/Target/Target.h"
20#include "lldb/lldb-types.h"
21#include "llvm/Support/Error.h"
22#include "llvm/Support/JSON.h"
23#include <fstream>
24#include <iostream>
25#include <optional>
26#include <sstream>
27#include <string>
28
29using namespace lldb;
30using namespace lldb_private;
31using namespace lldb_private::trace_intel_pt;
32using namespace llvm;
33
34/// Strip the \p directory component from the given \p path. It assumes that \p
35/// directory is a prefix of \p path.
36static std::string GetRelativePath(const FileSpec &directory,
37 const FileSpec &path) {
38 return path.GetPath().substr(directory.GetPath().size() + 1);
39}
40
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.
43static 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);
47 if (!data.empty())
48 out_fs.write(reinterpret_cast<const char *>(&data[0]), data.size());
49
50 out_fs.close();
51 if (!out_fs)
52 return createStringError(inconvertibleErrorCode(),
53 formatv("couldn't write to the file {0}",
54 output_file.GetPath().c_str()));
55 return Error::success();
56}
57
58/// Save the trace bundle description JSON object inside the given directory
59/// as a file named \a trace.json.
60///
61/// \param[in] trace_bundle_description
62/// The trace bundle description as JSON Object.
63///
64/// \param[in] directory
65/// The directory where the JSON file will be saved.
66///
67/// \return
68/// A \a FileSpec pointing to the bundle description file, or an \a
69/// llvm::Error otherwise.
70static Expected<FileSpec>
71SaveTraceBundleDescription(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();
77 os.close();
78 if (!os)
79 return createStringError(inconvertibleErrorCode(),
80 formatv("couldn't write to the file {0}",
81 trace_path.GetPath().c_str()));
82 return trace_path;
83}
84
85/// Build the threads sub-section of the trace bundle description file.
86/// Any associated binary files are created inside the given directory.
87///
88/// \param[in] process
89/// The process being traced.
90///
91/// \param[in] directory
92/// The directory where files will be saved when building the threads
93/// section.
94///
95/// \return
96/// The threads section or \a llvm::Error in case of failures.
97static llvm::Expected<std::vector<JSONThread>>
98BuildThreadsSection(Process &process, FileSpec directory) {
99 std::vector<JSONThread> json_threads;
100 TraceSP trace_sp = process.GetTarget().GetTrace();
101
102 FileSpec threads_dir = directory;
103 threads_dir.AppendPathComponent("threads");
104 sys::fs::create_directories(threads_dir.GetPath().c_str());
105
106 for (ThreadSP thread_sp : process.Threads()) {
107 lldb::tid_t tid = thread_sp->GetID();
108 if (!trace_sp->IsTraced(tid))
109 continue;
110
111 JSONThread json_thread;
112 json_thread.tid = tid;
113
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);
118
119 llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead(
121 [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
122 return WriteBytesToDisk(output_file, data);
123 });
124 if (err)
125 return std::move(err);
126 }
127
128 json_threads.push_back(std::move(json_thread));
129 }
130 return json_threads;
131}
132
133/// \return
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.
137static Expected<std::optional<FileSpec>>
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");
143
144 bool should_skip = false;
145
146 Error err = trace_ipt.OnCpuBinaryDataRead(
148 [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
149 if (!compact)
150 return WriteBytesToDisk(output_context_switch_trace, data);
151
152 std::set<lldb::pid_t> pids;
153 for (Process *process : trace_ipt.GetAllProcesses())
154 pids.insert(process->GetID());
155
156 Expected<std::vector<uint8_t>> compact_context_switch_trace =
158 if (!compact_context_switch_trace)
159 return compact_context_switch_trace.takeError();
160
161 if (compact_context_switch_trace->empty()) {
162 should_skip = true;
163 return Error::success();
164 }
165
166 return WriteBytesToDisk(output_context_switch_trace,
167 *compact_context_switch_trace);
168 });
169 if (err)
170 return std::move(err);
171
172 if (should_skip)
173 return std::nullopt;
174 return output_context_switch_trace;
175}
176
177static 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");
182
183 Error err = trace_ipt.OnCpuBinaryDataRead(
185 [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
186 return WriteBytesToDisk(output_trace, data);
187 });
188 if (err)
189 return std::move(err);
190 return output_trace;
191}
192
193static llvm::Expected<std::optional<std::vector<JSONCpu>>>
194BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact) {
195 if (trace_ipt.GetTracedCpus().empty())
196 return std::nullopt;
197
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());
202
203 for (lldb::cpu_id_t cpu_id : trace_ipt.GetTracedCpus()) {
204 JSONCpu json_cpu;
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)
211 continue;
212 json_cpu.context_switch_trace =
213 GetRelativePath(directory, **context_switch_trace_path);
214
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);
218 else
219 return ipt_trace_path.takeError();
220
221 json_cpus.push_back(std::move(json_cpu));
222 }
223 return json_cpus;
224}
225
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
228/// are skipped.
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.
233///
234/// \param[in] process
235/// The process being traced.
236///
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.
243///
244/// \return
245/// The modules section or \a llvm::Error in case of failures.
246static llvm::Expected<std::vector<JSONModule>>
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));
252 if (!module_sp)
253 continue;
254 std::string system_path = module_sp->GetPlatformFileSpec().GetPath();
255 // TODO: support memory-only libraries like [vdso]
256 if (!module_sp->GetFileSpec().IsAbsolute())
257 continue;
258
259 std::string file = module_sp->GetFileSpec().GetPath();
260 ObjectFile *objfile = module_sp->GetObjectFile();
261 if (objfile == nullptr)
262 continue;
263
265 Address base_addr(objfile->GetBaseAddress());
266 if (base_addr.IsValid() &&
267 !process.GetTarget().GetSectionLoadList().IsEmpty())
268 load_addr = base_addr.GetLoadAddress(&process.GetTarget());
269
270 if (load_addr == LLDB_INVALID_ADDRESS)
271 continue;
272
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());
277
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()));
283
284 json_modules.push_back(
285 JSONModule{system_path, GetRelativePath(directory, path_to_copy_module),
286 JSONUINT64{load_addr}, module_sp->GetUUID().GetAsString()});
287 }
288 return json_modules;
289}
290
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.
294///
295/// \param[in] process
296/// The process being traced.
297///
298/// \param[in] directory
299/// The directory where files will be saved when building the processes
300/// section.
301///
302/// \return
303/// The processes section or \a llvm::Error in case of failures.
304static llvm::Expected<JSONProcess>
305BuildProcessSection(Process &process, const FileSpec &directory) {
306 Expected<std::vector<JSONThread>> json_threads =
307 BuildThreadsSection(process, directory);
308 if (!json_threads)
309 return json_threads.takeError();
310
311 Expected<std::vector<JSONModule>> json_modules =
312 BuildModulesSection(process, directory);
313 if (!json_modules)
314 return json_modules.takeError();
315
316 return JSONProcess{
317 process.GetID(),
318 process.GetTarget().GetArchitecture().GetTriple().getTriple(),
319 json_threads.get(), json_modules.get()};
320}
321
322/// See BuildProcessSection()
323static llvm::Expected<std::vector<JSONProcess>>
324BuildProcessesSection(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));
330 else
331 return json_process.takeError();
332 }
333 return processes;
334}
335
336static llvm::Expected<JSONKernel>
337BuildKernelSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {
338 JSONKernel json_kernel;
339 std::vector<Process *> processes = trace_ipt.GetAllProcesses();
340 Process *kernel_process = processes[0];
341
342 assert(processes.size() == 1 && "User processeses exist in kernel mode");
343 assert(kernel_process->GetID() == kDefaultKernelProcessID &&
344 "Kernel process not exist");
345
346 Expected<std::vector<JSONModule>> json_modules =
347 BuildModulesSection(*kernel_process, directory);
348 if (!json_modules)
349 return json_modules.takeError();
350
351 JSONModule kernel_image = json_modules.get()[0];
352 return JSONKernel{kernel_image.load_address, kernel_image.system_path};
353}
354
356 FileSpec directory,
357 bool compact) {
358 if (std::error_code ec =
359 sys::fs::create_directories(directory.GetPath().c_str()))
360 return llvm::errorCodeToError(ec);
361
362 Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();
363 if (!cpu_info)
364 return cpu_info.takeError();
365
366 FileSystem::Instance().Resolve(directory);
367
368 Expected<std::optional<std::vector<JSONCpu>>> json_cpus =
369 BuildCpusSection(trace_ipt, directory, compact);
370 if (!json_cpus)
371 return json_cpus.takeError();
372
373 std::optional<std::vector<JSONProcess>> json_processes;
374 std::optional<JSONKernel> json_kernel;
375
377 Expected<std::optional<JSONKernel>> exp_json_kernel =
378 BuildKernelSection(trace_ipt, directory);
379 if (!exp_json_kernel)
380 return exp_json_kernel.takeError();
381 else
382 json_kernel = *exp_json_kernel;
383 } else {
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();
388 else
389 json_processes = *exp_json_processes;
390 }
391
392 JSONTraceBundleDescription json_intel_pt_bundle_desc{
393 "intel-pt",
394 *cpu_info,
395 json_processes,
396 *json_cpus,
397 trace_ipt.GetPerfZeroTscConversion(),
398 json_kernel};
399
400 return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc),
401 directory);
402}
static llvm::Expected< JSONProcess > BuildProcessSection(Process &process, const FileSpec &directory)
Build the processes section of the trace bundle description object.
static llvm::Expected< std::optional< std::vector< JSONCpu > > > BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact)
static std::string GetRelativePath(const FileSpec &directory, const FileSpec &path)
Strip the directory component from the given path.
static llvm::Expected< std::vector< JSONModule > > BuildModulesSection(Process &process, FileSpec directory)
Build modules sub-section of the trace bundle.
static llvm::Expected< std::vector< JSONThread > > BuildThreadsSection(Process &process, FileSpec directory)
Build the threads sub-section of the trace bundle description file.
static Expected< std::optional< FileSpec > > WriteContextSwitchTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id, const FileSpec &cpus_dir, bool compact)
static llvm::Error WriteBytesToDisk(FileSpec &output_file, ArrayRef< uint8_t > data)
Write a stream of bytes from data to the given output file.
static Expected< FileSpec > SaveTraceBundleDescription(const llvm::json::Value &trace_bundle_description, const FileSpec &directory)
Save the trace bundle description JSON object inside the given directory as a file named trace....
static Expected< FileSpec > WriteIntelPTTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id, const FileSpec &cpus_dir)
static llvm::Expected< JSONKernel > BuildKernelSection(TraceIntelPT &trace_ipt, const FileSpec &directory)
static llvm::Expected< std::vector< JSONProcess > > BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory)
See BuildProcessSection()
llvm::Error Error
A section + offset based address class.
Definition: Address.h:62
lldb::addr_t GetLoadAddress(Target *target) const
Get the load address.
Definition: Address.cpp:313
bool IsValid() const
Check if the object state is valid.
Definition: Address.h:355
llvm::Triple & GetTriple()
Architecture triple accessor.
Definition: ArchSpec.h:450
const char * AsCString(const char *value_if_empty=nullptr) const
Get the string value as a C string.
Definition: ConstString.h:188
A file utility class.
Definition: FileSpec.h:56
void AppendPathComponent(llvm::StringRef component)
Definition: FileSpec.cpp:447
const ConstString & GetDirectory() const
Directory string const get accessor.
Definition: FileSpec.h:223
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition: FileSpec.cpp:367
void Resolve(llvm::SmallVectorImpl< char > &path)
Resolve path to make it canonical.
static FileSystem & Instance()
A collection class for Module objects.
Definition: ModuleList.h:103
lldb::ModuleSP GetModuleAtIndex(size_t idx) const
Get the module shared pointer for the module at index idx.
Definition: ModuleList.cpp:429
size_t GetSize() const
Gets the size of the module list.
Definition: ModuleList.cpp:638
A plug-in interface definition class for object file parsers.
Definition: ObjectFile.h:44
virtual lldb_private::Address GetBaseAddress()
Returns base address of this object file.
Definition: ObjectFile.h:479
A plug-in interface definition class for debugging a process.
Definition: Process.h:341
lldb::pid_t GetID() const
Returns the pid of the process or LLDB_INVALID_PROCESS_ID if there is no known pid.
Definition: Process.h:538
ThreadList::ThreadIterable Threads()
Definition: Process.h:2226
Target & GetTarget()
Get the target object pointer for this module.
Definition: Process.h:1277
lldb::TraceSP GetTrace()
Get the Trace object containing processor trace information of this target.
Definition: Target.cpp:3375
SectionLoadList & GetSectionLoadList()
Definition: Target.h:1127
const ModuleList & GetImages() const
Get accessor for the images for this process.
Definition: Target.h:972
const ArchSpec & GetArchitecture() const
Definition: Target.h:1014
llvm::ArrayRef< lldb::cpu_id_t > GetTracedCpus()
Definition: Trace.cpp:513
std::vector< Process * > GetAllProcesses()
Definition: Trace.cpp:363
llvm::Error OnCpuBinaryDataRead(lldb::cpu_id_t cpu_id, llvm::StringRef kind, OnBinaryDataReadCallback callback)
Fetch binary data associated with a cpu, either live or postmortem, and pass it to the given callback...
Definition: Trace.cpp:504
llvm::Expected< FileSpec > SaveToDisk(TraceIntelPT &trace_ipt, FileSpec directory, bool compact)
Save the Intel PT trace of a live process to the specified directory, which will be created if needed...
llvm::Expected< pt_cpu > GetCPUInfo()
Get or fetch the cpu information from, for example, /proc/cpuinfo.
std::optional< LinuxPerfZeroTscConversion > GetPerfZeroTscConversion()
Get or fetch the values used to convert to and from TSCs and nanos.
#define LLDB_INVALID_ADDRESS
Definition: lldb-defines.h:82
const lldb::pid_t kDefaultKernelProcessID
llvm::Expected< std::vector< uint8_t > > FilterProcessesFromContextSwitchTrace(llvm::ArrayRef< uint8_t > data, const std::set< lldb::pid_t > &pids)
json::Value toJSON(const JSONModule &module)
A class that represents a running process on the host machine.
Definition: SBAttachInfo.h:14
Definition: SBAddress.h:15
std::shared_ptr< lldb_private::Trace > TraceSP
Definition: lldb-forward.h:446
std::shared_ptr< lldb_private::Thread > ThreadSP
Definition: lldb-forward.h:438
uint32_t cpu_id_t
Definition: lldb-types.h:89
uint64_t addr_t
Definition: lldb-types.h:79
uint64_t tid_t
Definition: lldb-types.h:82
std::shared_ptr< lldb_private::Module > ModuleSP
Definition: lldb-forward.h:365
Definition: Debugger.h:53
Helper structure to help parse long numbers that can't be easily represented by a JSON number that is...