1 //===-- TraceIntelPTBundleLoader.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 "TraceIntelPTBundleLoader.h" 10 11 #include "../common/ThreadPostMortemTrace.h" 12 #include "TraceIntelPT.h" 13 #include "TraceIntelPTJSONStructs.h" 14 15 #include "lldb/Core/Debugger.h" 16 #include "lldb/Core/Module.h" 17 #include "lldb/Target/Process.h" 18 #include "lldb/Target/Target.h" 19 20 using namespace lldb; 21 using namespace lldb_private; 22 using namespace lldb_private::trace_intel_pt; 23 using namespace llvm; 24 25 FileSpec TraceIntelPTBundleLoader::NormalizePath(const std::string &path) { 26 FileSpec file_spec(path); 27 if (file_spec.IsRelative()) 28 file_spec.PrependPathComponent(m_bundle_dir); 29 return file_spec; 30 } 31 32 Error TraceIntelPTBundleLoader::ParseModule(Target &target, 33 const JSONModule &module) { 34 auto do_parse = [&]() -> Error { 35 FileSpec system_file_spec(module.system_path); 36 37 FileSpec local_file_spec(module.file.hasValue() ? *module.file 38 : module.system_path); 39 40 ModuleSpec module_spec; 41 module_spec.GetFileSpec() = local_file_spec; 42 module_spec.GetPlatformFileSpec() = system_file_spec; 43 44 if (module.uuid.hasValue()) 45 module_spec.GetUUID().SetFromStringRef(*module.uuid); 46 47 Status error; 48 ModuleSP module_sp = 49 target.GetOrCreateModule(module_spec, /*notify*/ false, &error); 50 51 if (error.Fail()) 52 return error.ToError(); 53 54 bool load_addr_changed = false; 55 module_sp->SetLoadAddress(target, module.load_address.value, false, 56 load_addr_changed); 57 return Error::success(); 58 }; 59 if (Error err = do_parse()) 60 return createStringError( 61 inconvertibleErrorCode(), "Error when parsing module %s. %s", 62 module.system_path.c_str(), toString(std::move(err)).c_str()); 63 return Error::success(); 64 } 65 66 Error TraceIntelPTBundleLoader::CreateJSONError(json::Path::Root &root, 67 const json::Value &value) { 68 std::string err; 69 raw_string_ostream os(err); 70 root.printErrorContext(value, os); 71 return createStringError( 72 std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s", 73 toString(root.getError()).c_str(), os.str().c_str(), GetSchema().data()); 74 } 75 76 ThreadPostMortemTraceSP 77 TraceIntelPTBundleLoader::ParseThread(Process &process, 78 const JSONThread &thread) { 79 lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid); 80 81 Optional<FileSpec> trace_file; 82 if (thread.ipt_trace) 83 trace_file = FileSpec(*thread.ipt_trace); 84 85 ThreadPostMortemTraceSP thread_sp = 86 std::make_shared<ThreadPostMortemTrace>(process, tid, trace_file); 87 process.GetThreadList().AddThread(thread_sp); 88 return thread_sp; 89 } 90 91 Expected<TraceIntelPTBundleLoader::ParsedProcess> 92 TraceIntelPTBundleLoader::ParseProcess(const JSONProcess &process) { 93 TargetSP target_sp; 94 Status error = m_debugger.GetTargetList().CreateTarget( 95 m_debugger, /*user_exe_path*/ StringRef(), process.triple.value_or(""), 96 eLoadDependentsNo, 97 /*platform_options*/ nullptr, target_sp); 98 99 if (!target_sp) 100 return error.ToError(); 101 102 ParsedProcess parsed_process; 103 parsed_process.target_sp = target_sp; 104 105 ProcessSP process_sp = target_sp->CreateProcess( 106 /*listener*/ nullptr, "trace", 107 /*crash_file*/ nullptr, 108 /*can_connect*/ false); 109 110 process_sp->SetID(static_cast<lldb::pid_t>(process.pid)); 111 112 for (const JSONThread &thread : process.threads) 113 parsed_process.threads.push_back(ParseThread(*process_sp, thread)); 114 115 for (const JSONModule &module : process.modules) 116 if (Error err = ParseModule(*target_sp, module)) 117 return std::move(err); 118 119 if (!process.threads.empty()) 120 process_sp->GetThreadList().SetSelectedThreadByIndexID(0); 121 122 // We invoke DidAttach to create a correct stopped state for the process and 123 // its threads. 124 ArchSpec process_arch; 125 process_sp->DidAttach(process_arch); 126 127 return parsed_process; 128 } 129 130 Expected<std::vector<TraceIntelPTBundleLoader::ParsedProcess>> 131 TraceIntelPTBundleLoader::LoadBundle( 132 const JSONTraceBundleDescription &bundle_description) { 133 std::vector<ParsedProcess> parsed_processes; 134 135 auto HandleError = [&](Error &&err) { 136 // Delete all targets that were created so far in case of failures 137 for (ParsedProcess &parsed_process : parsed_processes) 138 m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp); 139 return std::move(err); 140 }; 141 142 for (const JSONProcess &process : bundle_description.processes) { 143 if (Expected<ParsedProcess> parsed_process = ParseProcess(process)) 144 parsed_processes.push_back(std::move(*parsed_process)); 145 else 146 return HandleError(parsed_process.takeError()); 147 } 148 149 return parsed_processes; 150 } 151 152 StringRef TraceIntelPTBundleLoader::GetSchema() { 153 static std::string schema; 154 if (schema.empty()) { 155 schema = R"({ 156 "type": "intel-pt", 157 "cpuInfo": { 158 // CPU information gotten from, for example, /proc/cpuinfo. 159 160 "vendor": "GenuineIntel" | "unknown", 161 "family": integer, 162 "model": integer, 163 "stepping": integer 164 }, 165 "processes": [ 166 { 167 "pid": integer, 168 "triple"?: string, 169 // Optional clang/llvm target triple. 170 "threads": [ 171 // A list of known threads for the given process. When context switch 172 // data is provided, LLDB will automatically create threads for the 173 // this process whenever it finds new threads when traversing the 174 // context switches, so passing values to this list in this case is 175 // optional. 176 { 177 "tid": integer, 178 "iptTrace"?: string 179 // Path to the raw Intel PT buffer file for this thread. 180 } 181 ], 182 "modules": [ 183 { 184 "systemPath": string, 185 // Original path of the module at runtime. 186 "file"?: string, 187 // Path to a copy of the file if not available at "systemPath". 188 "loadAddress": integer | string decimal | hex string, 189 // Lowest address of the sections of the module loaded on memory. 190 "uuid"?: string, 191 // Build UUID for the file for sanity checks. 192 } 193 ] 194 } 195 ], 196 "cpus"?: [ 197 { 198 "id": integer, 199 // Id of this CPU core. 200 "iptTrace": string, 201 // Path to the raw Intel PT buffer for this cpu core. 202 "contextSwitchTrace": string, 203 // Path to the raw perf_event_open context switch trace file for this cpu core. 204 // The perf_event must have been configured with PERF_SAMPLE_TID and 205 // PERF_SAMPLE_TIME, as well as sample_id_all = 1. 206 } 207 ], 208 "tscPerfZeroConversion"?: { 209 // Values used to convert between TSCs and nanoseconds. See the time_zero 210 // section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html 211 // for for information. 212 213 "timeMult": integer, 214 "timeShift": integer, 215 "timeZero": integer | string decimal | hex string, 216 } 217 } 218 219 Notes: 220 221 - All paths are either absolute or relative to folder containing the bundle description file. 222 - "cpus" is provided if and only if processes[].threads[].iptTrace is not provided. 223 - "tscPerfZeroConversion" must be provided if "cpus" is provided. 224 })"; 225 } 226 return schema; 227 } 228 229 Error TraceIntelPTBundleLoader::AugmentThreadsFromContextSwitches( 230 JSONTraceBundleDescription &bundle_description) { 231 if (!bundle_description.cpus) 232 return Error::success(); 233 234 if (!bundle_description.tsc_perf_zero_conversion) 235 return createStringError(inconvertibleErrorCode(), 236 "TSC to nanos conversion values are needed when " 237 "context switch information is provided."); 238 239 DenseMap<lldb::pid_t, JSONProcess *> indexed_processes; 240 DenseMap<JSONProcess *, DenseSet<tid_t>> indexed_threads; 241 242 for (JSONProcess &process : bundle_description.processes) { 243 indexed_processes[process.pid] = &process; 244 for (JSONThread &thread : process.threads) 245 indexed_threads[&process].insert(thread.tid); 246 } 247 248 auto on_thread_seen = [&](lldb::pid_t pid, tid_t tid) { 249 auto proc = indexed_processes.find(pid); 250 if (proc == indexed_processes.end()) 251 return; 252 if (indexed_threads[proc->second].count(tid)) 253 return; 254 indexed_threads[proc->second].insert(tid); 255 proc->second->threads.push_back({tid, /*ipt_trace=*/None}); 256 }; 257 258 for (const JSONCpu &cpu : *bundle_description.cpus) { 259 Error err = Trace::OnDataFileRead( 260 FileSpec(cpu.context_switch_trace), 261 [&](ArrayRef<uint8_t> data) -> Error { 262 Expected<std::vector<ThreadContinuousExecution>> executions = 263 DecodePerfContextSwitchTrace(data, cpu.id, 264 *bundle_description.tsc_perf_zero_conversion); 265 if (!executions) 266 return executions.takeError(); 267 for (const ThreadContinuousExecution &execution : *executions) 268 on_thread_seen(execution.pid, execution.tid); 269 return Error::success(); 270 }); 271 if (err) 272 return err; 273 } 274 return Error::success(); 275 } 276 277 Expected<TraceSP> TraceIntelPTBundleLoader::CreateTraceIntelPTInstance( 278 JSONTraceBundleDescription &bundle_description, std::vector<ParsedProcess> &parsed_processes) { 279 std::vector<ThreadPostMortemTraceSP> threads; 280 std::vector<ProcessSP> processes; 281 for (const ParsedProcess &parsed_process : parsed_processes) { 282 processes.push_back(parsed_process.target_sp->GetProcessSP()); 283 threads.insert(threads.end(), parsed_process.threads.begin(), 284 parsed_process.threads.end()); 285 } 286 287 TraceSP trace_instance = TraceIntelPT::CreateInstanceForPostmortemTrace( 288 bundle_description, processes, threads); 289 for (const ParsedProcess &parsed_process : parsed_processes) 290 parsed_process.target_sp->SetTrace(trace_instance); 291 292 return trace_instance; 293 } 294 295 void TraceIntelPTBundleLoader::NormalizeAllPaths( 296 JSONTraceBundleDescription &bundle_description) { 297 for (JSONProcess &process : bundle_description.processes) { 298 for (JSONModule &module : process.modules) { 299 module.system_path = NormalizePath(module.system_path).GetPath(); 300 if (module.file) 301 module.file = NormalizePath(*module.file).GetPath(); 302 } 303 for (JSONThread &thread : process.threads) { 304 if (thread.ipt_trace) 305 thread.ipt_trace = NormalizePath(*thread.ipt_trace).GetPath(); 306 } 307 } 308 if (bundle_description.cpus) { 309 for (JSONCpu &cpu : *bundle_description.cpus) { 310 cpu.context_switch_trace = 311 NormalizePath(cpu.context_switch_trace).GetPath(); 312 cpu.ipt_trace = NormalizePath(cpu.ipt_trace).GetPath(); 313 } 314 } 315 } 316 317 Expected<TraceSP> TraceIntelPTBundleLoader::Load() { 318 json::Path::Root root("traceBundle"); 319 JSONTraceBundleDescription bundle_description; 320 if (!fromJSON(m_bundle_description, bundle_description, root)) 321 return CreateJSONError(root, m_bundle_description); 322 323 NormalizeAllPaths(bundle_description); 324 325 if (Error err = AugmentThreadsFromContextSwitches(bundle_description)) 326 return std::move(err); 327 328 if (Expected<std::vector<ParsedProcess>> parsed_processes = 329 LoadBundle(bundle_description)) 330 return CreateTraceIntelPTInstance(bundle_description, *parsed_processes); 331 else 332 return parsed_processes.takeError(); 333 } 334