Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.cpp
39648 views
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
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"
23
#include <fstream>
24
#include <iostream>
25
#include <optional>
26
#include <sstream>
27
#include <string>
28
29
using namespace lldb;
30
using namespace lldb_private;
31
using namespace lldb_private::trace_intel_pt;
32
using 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.
36
static 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.
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);
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.
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();
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.
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();
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(
120
tid, IntelPTDataKinds::kIptTrace,
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.
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");
143
144
bool should_skip = false;
145
146
Error err = trace_ipt.OnCpuBinaryDataRead(
147
cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace,
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 =
157
FilterProcessesFromContextSwitchTrace(data, pids);
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
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");
182
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);
187
});
188
if (err)
189
return std::move(err);
190
return output_trace;
191
}
192
193
static llvm::Expected<std::optional<std::vector<JSONCpu>>>
194
BuildCpusSection(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.
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));
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
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());
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.
304
static llvm::Expected<JSONProcess>
305
BuildProcessSection(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()
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));
330
else
331
return json_process.takeError();
332
}
333
return processes;
334
}
335
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];
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
355
Expected<FileSpec> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT &trace_ipt,
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
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();
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
}
403
404