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