1 //===-- Trace.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 "lldb/Target/Trace.h" 10 11 #include "llvm/Support/Format.h" 12 13 #include "lldb/Core/Module.h" 14 #include "lldb/Core/PluginManager.h" 15 #include "lldb/Symbol/Function.h" 16 #include "lldb/Target/Process.h" 17 #include "lldb/Target/SectionLoadList.h" 18 #include "lldb/Target/Thread.h" 19 #include "lldb/Target/ThreadPostMortemTrace.h" 20 #include "lldb/Utility/Stream.h" 21 22 using namespace lldb; 23 using namespace lldb_private; 24 using namespace llvm; 25 26 // Helper structs used to extract the type of a trace session json without 27 // having to parse the entire object. 28 29 struct JSONSimplePluginSettings { 30 std::string type; 31 }; 32 33 struct JSONSimpleTraceSession { 34 JSONSimplePluginSettings trace; 35 }; 36 37 namespace llvm { 38 namespace json { 39 40 bool fromJSON(const Value &value, JSONSimplePluginSettings &plugin_settings, 41 Path path) { 42 json::ObjectMapper o(value, path); 43 return o && o.map("type", plugin_settings.type); 44 } 45 46 bool fromJSON(const Value &value, JSONSimpleTraceSession &session, Path path) { 47 json::ObjectMapper o(value, path); 48 return o && o.map("trace", session.trace); 49 } 50 51 } // namespace json 52 } // namespace llvm 53 54 static Error createInvalidPlugInError(StringRef plugin_name) { 55 return createStringError( 56 std::errc::invalid_argument, 57 "no trace plug-in matches the specified type: \"%s\"", 58 plugin_name.data()); 59 } 60 61 Expected<lldb::TraceSP> 62 Trace::FindPluginForPostMortemProcess(Debugger &debugger, 63 const json::Value &trace_session_file, 64 StringRef session_file_dir) { 65 JSONSimpleTraceSession json_session; 66 json::Path::Root root("traceSession"); 67 if (!json::fromJSON(trace_session_file, json_session, root)) 68 return root.getError(); 69 70 ConstString plugin_name(json_session.trace.type); 71 if (auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name)) 72 return create_callback(trace_session_file, session_file_dir, debugger); 73 74 return createInvalidPlugInError(json_session.trace.type); 75 } 76 77 Expected<lldb::TraceSP> 78 Trace::FindPluginForLiveProcess(llvm::StringRef plugin_name, Process &process) { 79 if (!process.IsLiveDebugSession()) 80 return createStringError(inconvertibleErrorCode(), 81 "Can't trace non-live processes"); 82 83 ConstString name(plugin_name); 84 if (auto create_callback = 85 PluginManager::GetTraceCreateCallbackForLiveProcess(name)) 86 return create_callback(process); 87 88 return createInvalidPlugInError(plugin_name); 89 } 90 91 Expected<StringRef> Trace::FindPluginSchema(StringRef name) { 92 ConstString plugin_name(name); 93 StringRef schema = PluginManager::GetTraceSchema(plugin_name); 94 if (!schema.empty()) 95 return schema; 96 97 return createInvalidPlugInError(name); 98 } 99 100 static int GetNumberOfDigits(size_t num) { 101 return num == 0 ? 1 : static_cast<int>(log10(num)) + 1; 102 } 103 104 /// Dump the symbol context of the given instruction address if it's different 105 /// from the symbol context of the previous instruction in the trace. 106 /// 107 /// \param[in] prev_sc 108 /// The symbol context of the previous instruction in the trace. 109 /// 110 /// \param[in] address 111 /// The address whose symbol information will be dumped. 112 /// 113 /// \return 114 /// The symbol context of the current address, which might differ from the 115 /// previous one. 116 static SymbolContext DumpSymbolContext(Stream &s, const SymbolContext &prev_sc, 117 Target &target, const Address &address) { 118 AddressRange range; 119 if (prev_sc.GetAddressRange(eSymbolContextEverything, 0, 120 /*inline_block_range*/ false, range) && 121 range.ContainsFileAddress(address)) 122 return prev_sc; 123 124 SymbolContext sc; 125 address.CalculateSymbolContext(&sc, eSymbolContextEverything); 126 127 if (!prev_sc.module_sp && !sc.module_sp) 128 return sc; 129 if (prev_sc.module_sp == sc.module_sp && !sc.function && !sc.symbol && 130 !prev_sc.function && !prev_sc.symbol) 131 return sc; 132 133 s.Printf(" "); 134 135 if (!sc.module_sp) 136 s.Printf("(none)"); 137 else if (!sc.function && !sc.symbol) 138 s.Printf("%s`(none)", 139 sc.module_sp->GetFileSpec().GetFilename().AsCString()); 140 else 141 sc.DumpStopContext(&s, &target, address, /*show_fullpath*/ false, 142 /*show_module*/ true, /*show_inlined_frames*/ false, 143 /*show_function_arguments*/ true, 144 /*show_function_name*/ true); 145 s.Printf("\n"); 146 return sc; 147 } 148 149 /// Dump an instruction given by its address using a given disassembler, unless 150 /// the instruction is not present in the disassembler. 151 /// 152 /// \param[in] disassembler 153 /// A disassembler containing a certain instruction list. 154 /// 155 /// \param[in] address 156 /// The address of the instruction to dump. 157 /// 158 /// \return 159 /// \b true if the information could be dumped, \b false otherwise. 160 static bool TryDumpInstructionInfo(Stream &s, 161 const DisassemblerSP &disassembler, 162 const ExecutionContext &exe_ctx, 163 const Address &address) { 164 if (!disassembler) 165 return false; 166 167 if (InstructionSP instruction = 168 disassembler->GetInstructionList().GetInstructionAtAddress(address)) { 169 instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, 170 /*max_opcode_byte_size*/ 0, &exe_ctx, 171 /*sym_ctx*/ nullptr, /*prev_sym_ctx*/ nullptr, 172 /*disassembly_addr_format*/ nullptr, 173 /*max_address_text_size*/ 0); 174 return true; 175 } 176 177 return false; 178 } 179 180 /// Dump an instruction instruction given by its address. 181 /// 182 /// \param[in] prev_disassembler 183 /// The disassembler that was used to dump the previous instruction in the 184 /// trace. It is useful to avoid recomputations. 185 /// 186 /// \param[in] address 187 /// The address of the instruction to dump. 188 /// 189 /// \return 190 /// A disassembler that contains the given instruction, which might differ 191 /// from the previous disassembler. 192 static DisassemblerSP 193 DumpInstructionInfo(Stream &s, const SymbolContext &sc, 194 const DisassemblerSP &prev_disassembler, 195 ExecutionContext &exe_ctx, const Address &address) { 196 // We first try to use the previous disassembler 197 if (TryDumpInstructionInfo(s, prev_disassembler, exe_ctx, address)) 198 return prev_disassembler; 199 200 // Now we try using the current function's disassembler 201 if (sc.function) { 202 DisassemblerSP disassembler = 203 sc.function->GetInstructions(exe_ctx, nullptr, true); 204 if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address)) 205 return disassembler; 206 } 207 208 // We fallback to disassembly one instruction 209 Target &target = exe_ctx.GetTargetRef(); 210 const ArchSpec &arch = target.GetArchitecture(); 211 AddressRange range(address, arch.GetMaximumOpcodeByteSize() * 1); 212 DisassemblerSP disassembler = Disassembler::DisassembleRange( 213 arch, /*plugin_name*/ nullptr, 214 /*flavor*/ nullptr, target, range, /*prefer_file_cache*/ true); 215 if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address)) 216 return disassembler; 217 return nullptr; 218 } 219 220 void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, 221 size_t end_position, bool raw) { 222 if (!IsTraced(thread)) { 223 s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), 224 thread.GetID()); 225 return; 226 } 227 228 size_t instructions_count = GetInstructionCount(thread); 229 s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", 230 thread.GetIndexID(), thread.GetID(), instructions_count); 231 232 if (count == 0 || end_position >= instructions_count) 233 return; 234 235 size_t start_position = 236 end_position + 1 < count ? 0 : end_position + 1 - count; 237 238 int digits_count = GetNumberOfDigits(end_position); 239 auto printInstructionIndex = [&](size_t index) { 240 s.Printf(" [%*zu] ", digits_count, index); 241 }; 242 243 bool was_prev_instruction_an_error = false; 244 Target &target = thread.GetProcess()->GetTarget(); 245 246 SymbolContext sc; 247 DisassemblerSP disassembler; 248 ExecutionContext exe_ctx; 249 target.CalculateExecutionContext(exe_ctx); 250 251 TraverseInstructions( 252 thread, start_position, TraceDirection::Forwards, 253 [&](size_t index, Expected<lldb::addr_t> load_address) -> bool { 254 if (load_address) { 255 // We print an empty line after a sequence of errors to show more 256 // clearly that there's a gap in the trace 257 if (was_prev_instruction_an_error) 258 s.Printf(" ...missing instructions\n"); 259 260 Address address; 261 if (!raw) { 262 target.GetSectionLoadList().ResolveLoadAddress(*load_address, 263 address); 264 265 sc = DumpSymbolContext(s, sc, target, address); 266 } 267 268 printInstructionIndex(index); 269 s.Printf("0x%016" PRIx64 " ", *load_address); 270 271 if (!raw) { 272 disassembler = 273 DumpInstructionInfo(s, sc, disassembler, exe_ctx, address); 274 } 275 276 was_prev_instruction_an_error = false; 277 } else { 278 printInstructionIndex(index); 279 s << toString(load_address.takeError()); 280 was_prev_instruction_an_error = true; 281 if (!raw) 282 sc = SymbolContext(); 283 } 284 285 s.Printf("\n"); 286 287 return index < end_position; 288 }); 289 } 290 291 Error Trace::Start(const llvm::json::Value &request) { 292 if (!m_live_process) 293 return createStringError(inconvertibleErrorCode(), 294 "Tracing requires a live process."); 295 return m_live_process->TraceStart(request); 296 } 297 298 Error Trace::StopProcess() { 299 if (!m_live_process) 300 return createStringError(inconvertibleErrorCode(), 301 "Tracing requires a live process."); 302 return m_live_process->TraceStop( 303 TraceStopRequest(GetPluginName().AsCString())); 304 } 305 306 Error Trace::StopThreads(const std::vector<lldb::tid_t> &tids) { 307 if (!m_live_process) 308 return createStringError(inconvertibleErrorCode(), 309 "Tracing requires a live process."); 310 return m_live_process->TraceStop( 311 TraceStopRequest(GetPluginName().AsCString(), tids)); 312 } 313 314 Expected<std::string> Trace::GetLiveProcessState() { 315 if (!m_live_process) 316 return createStringError(inconvertibleErrorCode(), 317 "Tracing requires a live process."); 318 return m_live_process->TraceGetState(GetPluginName().AsCString()); 319 } 320 321 Optional<size_t> Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, 322 llvm::StringRef kind) { 323 auto it = m_live_thread_data.find(tid); 324 if (it == m_live_thread_data.end()) 325 return None; 326 std::unordered_map<std::string, size_t> &single_thread_data = it->second; 327 auto single_thread_data_it = single_thread_data.find(kind.str()); 328 if (single_thread_data_it == single_thread_data.end()) 329 return None; 330 return single_thread_data_it->second; 331 } 332 333 Optional<size_t> Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { 334 auto data_it = m_live_process_data.find(kind.str()); 335 if (data_it == m_live_process_data.end()) 336 return None; 337 return data_it->second; 338 } 339 340 Expected<std::vector<uint8_t>> 341 Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { 342 if (!m_live_process) 343 return createStringError(inconvertibleErrorCode(), 344 "Tracing requires a live process."); 345 llvm::Optional<size_t> size = GetLiveThreadBinaryDataSize(tid, kind); 346 if (!size) 347 return createStringError( 348 inconvertibleErrorCode(), 349 "Tracing data \"%s\" is not available for thread %" PRIu64 ".", 350 kind.data(), tid); 351 352 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 353 static_cast<int64_t>(tid), 0, 354 static_cast<int64_t>(*size)}; 355 return m_live_process->TraceGetBinaryData(request); 356 } 357 358 Expected<std::vector<uint8_t>> 359 Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { 360 if (!m_live_process) 361 return createStringError(inconvertibleErrorCode(), 362 "Tracing requires a live process."); 363 llvm::Optional<size_t> size = GetLiveProcessBinaryDataSize(kind); 364 if (!size) 365 return createStringError( 366 inconvertibleErrorCode(), 367 "Tracing data \"%s\" is not available for the process.", kind.data()); 368 369 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 370 None, 0, static_cast<int64_t>(*size)}; 371 return m_live_process->TraceGetBinaryData(request); 372 } 373 374 void Trace::RefreshLiveProcessState() { 375 if (!m_live_process) 376 return; 377 378 uint32_t new_stop_id = m_live_process->GetStopID(); 379 if (new_stop_id == m_stop_id) 380 return; 381 382 m_stop_id = new_stop_id; 383 m_live_thread_data.clear(); 384 385 Expected<std::string> json_string = GetLiveProcessState(); 386 if (!json_string) { 387 DoRefreshLiveProcessState(json_string.takeError()); 388 return; 389 } 390 Expected<TraceGetStateResponse> live_process_state = 391 json::parse<TraceGetStateResponse>(*json_string, "TraceGetStateResponse"); 392 if (!live_process_state) { 393 DoRefreshLiveProcessState(live_process_state.takeError()); 394 return; 395 } 396 397 for (const TraceThreadState &thread_state : 398 live_process_state->tracedThreads) { 399 for (const TraceBinaryData &item : thread_state.binaryData) 400 m_live_thread_data[thread_state.tid][item.kind] = item.size; 401 } 402 403 for (const TraceBinaryData &item : live_process_state->processBinaryData) 404 m_live_process_data[item.kind] = item.size; 405 406 DoRefreshLiveProcessState(std::move(live_process_state)); 407 } 408