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
11 #include "PerfContextSwitchDecoder.h"
12 #include "TraceIntelPT.h"
13 #include "TraceIntelPTJSONStructs.h"
14
15 #include "lldb/Core/Module.h"
16 #include "lldb/Core/ModuleList.h"
17 #include "lldb/Target/Process.h"
18 #include "lldb/Target/SectionLoadList.h"
19 #include "lldb/Target/Target.h"
20 #include "lldb/Target/ThreadList.h"
21 #include "lldb/lldb-types.h"
22 #include "llvm/ADT/None.h"
23 #include "llvm/Support/Error.h"
24 #include "llvm/Support/JSON.h"
25
26 #include <fstream>
27 #include <iostream>
28 #include <sstream>
29 #include <string>
30
31 using namespace lldb;
32 using namespace lldb_private;
33 using namespace lldb_private::trace_intel_pt;
34 using namespace llvm;
35
36 /// Strip the \p directory component from the given \p path. It assumes that \p
37 /// directory is a prefix of \p path.
GetRelativePath(const FileSpec & directory,const FileSpec & path)38 static std::string GetRelativePath(const FileSpec &directory,
39 const FileSpec &path) {
40 return path.GetPath().substr(directory.GetPath().size() + 1);
41 }
42
43 /// Write a stream of bytes from \p data to the given output file.
44 /// It creates or overwrites the output file, but not append.
WriteBytesToDisk(FileSpec & output_file,ArrayRef<uint8_t> data)45 static llvm::Error WriteBytesToDisk(FileSpec &output_file,
46 ArrayRef<uint8_t> data) {
47 std::basic_fstream<char> out_fs = std::fstream(
48 output_file.GetPath().c_str(), std::ios::out | std::ios::binary);
49 if (!data.empty())
50 out_fs.write(reinterpret_cast<const char *>(&data[0]), data.size());
51
52 out_fs.close();
53 if (!out_fs)
54 return createStringError(inconvertibleErrorCode(),
55 formatv("couldn't write to the file {0}",
56 output_file.GetPath().c_str()));
57 return Error::success();
58 }
59
60 /// Save the trace bundle description JSON object inside the given directory
61 /// as a file named \a trace.json.
62 ///
63 /// \param[in] trace_bundle_description
64 /// The trace bundle description as JSON Object.
65 ///
66 /// \param[in] directory
67 /// The directory where the JSON file will be saved.
68 ///
69 /// \return
70 /// A \a FileSpec pointing to the bundle description file, or an \a
71 /// llvm::Error otherwise.
72 static Expected<FileSpec>
SaveTraceBundleDescription(const llvm::json::Value & trace_bundle_description,const FileSpec & directory)73 SaveTraceBundleDescription(const llvm::json::Value &trace_bundle_description,
74 const FileSpec &directory) {
75 FileSpec trace_path = directory;
76 trace_path.AppendPathComponent("trace.json");
77 std::ofstream os(trace_path.GetPath());
78 os << formatv("{0:2}", trace_bundle_description).str();
79 os.close();
80 if (!os)
81 return createStringError(inconvertibleErrorCode(),
82 formatv("couldn't write to the file {0}",
83 trace_path.GetPath().c_str()));
84 return trace_path;
85 }
86
87 /// Build the threads sub-section of the trace bundle description file.
88 /// Any associated binary files are created inside the given directory.
89 ///
90 /// \param[in] process
91 /// The process being traced.
92 ///
93 /// \param[in] directory
94 /// The directory where files will be saved when building the threads
95 /// section.
96 ///
97 /// \return
98 /// The threads section or \a llvm::Error in case of failures.
99 static llvm::Expected<std::vector<JSONThread>>
BuildThreadsSection(Process & process,FileSpec directory)100 BuildThreadsSection(Process &process, FileSpec directory) {
101 std::vector<JSONThread> json_threads;
102 TraceSP trace_sp = process.GetTarget().GetTrace();
103
104 FileSpec threads_dir = directory;
105 threads_dir.AppendPathComponent("threads");
106 sys::fs::create_directories(threads_dir.GetCString());
107
108 for (ThreadSP thread_sp : process.Threads()) {
109 lldb::tid_t tid = thread_sp->GetID();
110 if (!trace_sp->IsTraced(tid))
111 continue;
112
113 JSONThread json_thread;
114 json_thread.tid = tid;
115
116 if (trace_sp->GetTracedCpus().empty()) {
117 FileSpec output_file = threads_dir;
118 output_file.AppendPathComponent(std::to_string(tid) + ".intelpt_trace");
119 json_thread.ipt_trace = GetRelativePath(directory, output_file);
120
121 llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead(
122 tid, IntelPTDataKinds::kIptTrace,
123 [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
124 return WriteBytesToDisk(output_file, data);
125 });
126 if (err)
127 return std::move(err);
128 }
129
130 json_threads.push_back(std::move(json_thread));
131 }
132 return json_threads;
133 }
134
135 /// \return
136 /// an \a llvm::Error in case of failures, \a None if the trace is not written
137 /// to disk because the trace is empty and the \p compact flag is present, or
138 /// the FileSpec of the trace file on disk.
139 static Expected<Optional<FileSpec>>
WriteContextSwitchTrace(TraceIntelPT & trace_ipt,lldb::cpu_id_t cpu_id,const FileSpec & cpus_dir,bool compact)140 WriteContextSwitchTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id,
141 const FileSpec &cpus_dir, bool compact) {
142 FileSpec output_context_switch_trace = cpus_dir;
143 output_context_switch_trace.AppendPathComponent(std::to_string(cpu_id) +
144 ".perf_context_switch_trace");
145
146 bool should_skip = false;
147
148 Error err = trace_ipt.OnCpuBinaryDataRead(
149 cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace,
150 [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
151 if (!compact)
152 return WriteBytesToDisk(output_context_switch_trace, data);
153
154 std::set<lldb::pid_t> pids;
155 for (Process *process : trace_ipt.GetAllProcesses())
156 pids.insert(process->GetID());
157
158 Expected<std::vector<uint8_t>> compact_context_switch_trace =
159 FilterProcessesFromContextSwitchTrace(data, pids);
160 if (!compact_context_switch_trace)
161 return compact_context_switch_trace.takeError();
162
163 if (compact_context_switch_trace->empty()) {
164 should_skip = true;
165 return Error::success();
166 }
167
168 return WriteBytesToDisk(output_context_switch_trace,
169 *compact_context_switch_trace);
170 });
171 if (err)
172 return std::move(err);
173
174 if (should_skip)
175 return None;
176 return output_context_switch_trace;
177 }
178
WriteIntelPTTrace(TraceIntelPT & trace_ipt,lldb::cpu_id_t cpu_id,const FileSpec & cpus_dir)179 static Expected<FileSpec> WriteIntelPTTrace(TraceIntelPT &trace_ipt,
180 lldb::cpu_id_t cpu_id,
181 const FileSpec &cpus_dir) {
182 FileSpec output_trace = cpus_dir;
183 output_trace.AppendPathComponent(std::to_string(cpu_id) + ".intelpt_trace");
184
185 Error err = trace_ipt.OnCpuBinaryDataRead(
186 cpu_id, IntelPTDataKinds::kIptTrace,
187 [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
188 return WriteBytesToDisk(output_trace, data);
189 });
190 if (err)
191 return std::move(err);
192 return output_trace;
193 }
194
195 static llvm::Expected<llvm::Optional<std::vector<JSONCpu>>>
BuildCpusSection(TraceIntelPT & trace_ipt,FileSpec directory,bool compact)196 BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact) {
197 if (trace_ipt.GetTracedCpus().empty())
198 return None;
199
200 std::vector<JSONCpu> json_cpus;
201 FileSpec cpus_dir = directory;
202 cpus_dir.AppendPathComponent("cpus");
203 sys::fs::create_directories(cpus_dir.GetCString());
204
205 for (lldb::cpu_id_t cpu_id : trace_ipt.GetTracedCpus()) {
206 JSONCpu json_cpu;
207 json_cpu.id = cpu_id;
208 Expected<Optional<FileSpec>> context_switch_trace_path =
209 WriteContextSwitchTrace(trace_ipt, cpu_id, cpus_dir, compact);
210 if (!context_switch_trace_path)
211 return context_switch_trace_path.takeError();
212 if (!*context_switch_trace_path)
213 continue;
214 json_cpu.context_switch_trace =
215 GetRelativePath(directory, **context_switch_trace_path);
216
217 if (Expected<FileSpec> ipt_trace_path =
218 WriteIntelPTTrace(trace_ipt, cpu_id, cpus_dir))
219 json_cpu.ipt_trace = GetRelativePath(directory, *ipt_trace_path);
220 else
221 return ipt_trace_path.takeError();
222
223 json_cpus.push_back(std::move(json_cpu));
224 }
225 return json_cpus;
226 }
227
228 /// Build modules sub-section of the trace bundle. The original modules
229 /// will be copied over to the \a <directory/modules> folder. Invalid modules
230 /// are skipped.
231 /// Copying the modules has the benefit of making these
232 /// directories self-contained, as the raw traces and modules are part of the
233 /// output directory and can be sent to another machine, where lldb can load
234 /// them and replicate exactly the same trace session.
235 ///
236 /// \param[in] process
237 /// The process being traced.
238 ///
239 /// \param[in] directory
240 /// The directory where the modules files will be saved when building
241 /// the modules section.
242 /// Example: If a module \a libbar.so exists in the path
243 /// \a /usr/lib/foo/libbar.so, then it will be copied to
244 /// \a <directory>/modules/usr/lib/foo/libbar.so.
245 ///
246 /// \return
247 /// The modules section or \a llvm::Error in case of failures.
248 static llvm::Expected<std::vector<JSONModule>>
BuildModulesSection(Process & process,FileSpec directory)249 BuildModulesSection(Process &process, FileSpec directory) {
250 std::vector<JSONModule> json_modules;
251 ModuleList module_list = process.GetTarget().GetImages();
252 for (size_t i = 0; i < module_list.GetSize(); ++i) {
253 ModuleSP module_sp(module_list.GetModuleAtIndex(i));
254 if (!module_sp)
255 continue;
256 std::string system_path = module_sp->GetPlatformFileSpec().GetPath();
257 // TODO: support memory-only libraries like [vdso]
258 if (!module_sp->GetFileSpec().IsAbsolute())
259 continue;
260
261 std::string file = module_sp->GetFileSpec().GetPath();
262 ObjectFile *objfile = module_sp->GetObjectFile();
263 if (objfile == nullptr)
264 continue;
265
266 lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
267 Address base_addr(objfile->GetBaseAddress());
268 if (base_addr.IsValid() &&
269 !process.GetTarget().GetSectionLoadList().IsEmpty())
270 load_addr = base_addr.GetLoadAddress(&process.GetTarget());
271
272 if (load_addr == LLDB_INVALID_ADDRESS)
273 continue;
274
275 FileSpec path_to_copy_module = directory;
276 path_to_copy_module.AppendPathComponent("modules");
277 path_to_copy_module.AppendPathComponent(system_path);
278 sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString());
279
280 if (std::error_code ec =
281 llvm::sys::fs::copy_file(file, path_to_copy_module.GetPath()))
282 return createStringError(
283 inconvertibleErrorCode(),
284 formatv("couldn't write to the file. {0}", ec.message()));
285
286 json_modules.push_back(
287 JSONModule{system_path, GetRelativePath(directory, path_to_copy_module),
288 JSONUINT64{load_addr}, module_sp->GetUUID().GetAsString()});
289 }
290 return json_modules;
291 }
292
293 /// Build the processes section of the trace bundle description object. Besides
294 /// returning the processes information, this method saves to disk all modules
295 /// and raw traces corresponding to the traced threads of the given process.
296 ///
297 /// \param[in] process
298 /// The process being traced.
299 ///
300 /// \param[in] directory
301 /// The directory where files will be saved when building the processes
302 /// section.
303 ///
304 /// \return
305 /// The processes section or \a llvm::Error in case of failures.
306 static llvm::Expected<JSONProcess>
BuildProcessSection(Process & process,const FileSpec & directory)307 BuildProcessSection(Process &process, const FileSpec &directory) {
308 Expected<std::vector<JSONThread>> json_threads =
309 BuildThreadsSection(process, directory);
310 if (!json_threads)
311 return json_threads.takeError();
312
313 Expected<std::vector<JSONModule>> json_modules =
314 BuildModulesSection(process, directory);
315 if (!json_modules)
316 return json_modules.takeError();
317
318 return JSONProcess{
319 process.GetID(),
320 process.GetTarget().GetArchitecture().GetTriple().getTriple(),
321 json_threads.get(), json_modules.get()};
322 }
323
324 /// See BuildProcessSection()
325 static llvm::Expected<std::vector<JSONProcess>>
BuildProcessesSection(TraceIntelPT & trace_ipt,const FileSpec & directory)326 BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {
327 std::vector<JSONProcess> processes;
328 for (Process *process : trace_ipt.GetAllProcesses()) {
329 if (llvm::Expected<JSONProcess> json_process =
330 BuildProcessSection(*process, directory))
331 processes.push_back(std::move(*json_process));
332 else
333 return json_process.takeError();
334 }
335 return processes;
336 }
337
SaveToDisk(TraceIntelPT & trace_ipt,FileSpec directory,bool compact)338 Expected<FileSpec> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT &trace_ipt,
339 FileSpec directory,
340 bool compact) {
341 if (std::error_code ec =
342 sys::fs::create_directories(directory.GetPath().c_str()))
343 return llvm::errorCodeToError(ec);
344
345 Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();
346 if (!cpu_info)
347 return cpu_info.takeError();
348
349 FileSystem::Instance().Resolve(directory);
350
351 Expected<std::vector<JSONProcess>> json_processes =
352 BuildProcessesSection(trace_ipt, directory);
353
354 if (!json_processes)
355 return json_processes.takeError();
356
357 Expected<Optional<std::vector<JSONCpu>>> json_cpus =
358 BuildCpusSection(trace_ipt, directory, compact);
359 if (!json_cpus)
360 return json_cpus.takeError();
361
362 JSONTraceBundleDescription json_intel_pt_bundle_desc{"intel-pt", *cpu_info, *json_processes,
363 *json_cpus,
364 trace_ipt.GetPerfZeroTscConversion()};
365
366 return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc), directory);
367 }
368