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