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. 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. 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> 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>> 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>> 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 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>>> 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>> 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> 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>> 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 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