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/ExecutionContext.h" 17 #include "lldb/Target/Process.h" 18 #include "lldb/Target/SectionLoadList.h" 19 #include "lldb/Target/Thread.h" 20 #include "lldb/Target/ThreadPostMortemTrace.h" 21 #include "lldb/Utility/Stream.h" 22 23 using namespace lldb; 24 using namespace lldb_private; 25 using namespace llvm; 26 27 // Helper structs used to extract the type of a trace session json without 28 // having to parse the entire object. 29 30 struct JSONSimplePluginSettings { 31 std::string type; 32 }; 33 34 struct JSONSimpleTraceSession { 35 JSONSimplePluginSettings trace; 36 }; 37 38 namespace llvm { 39 namespace json { 40 41 bool fromJSON(const Value &value, JSONSimplePluginSettings &plugin_settings, 42 Path path) { 43 json::ObjectMapper o(value, path); 44 return o && o.map("type", plugin_settings.type); 45 } 46 47 bool fromJSON(const Value &value, JSONSimpleTraceSession &session, Path path) { 48 json::ObjectMapper o(value, path); 49 return o && o.map("trace", session.trace); 50 } 51 52 } // namespace json 53 } // namespace llvm 54 55 static Error createInvalidPlugInError(StringRef plugin_name) { 56 return createStringError( 57 std::errc::invalid_argument, 58 "no trace plug-in matches the specified type: \"%s\"", 59 plugin_name.data()); 60 } 61 62 Expected<lldb::TraceSP> 63 Trace::FindPluginForPostMortemProcess(Debugger &debugger, 64 const json::Value &trace_session_file, 65 StringRef session_file_dir) { 66 JSONSimpleTraceSession json_session; 67 json::Path::Root root("traceSession"); 68 if (!json::fromJSON(trace_session_file, json_session, root)) 69 return root.getError(); 70 71 ConstString plugin_name(json_session.trace.type); 72 if (auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name)) 73 return create_callback(trace_session_file, session_file_dir, debugger); 74 75 return createInvalidPlugInError(json_session.trace.type); 76 } 77 78 Expected<lldb::TraceSP> 79 Trace::FindPluginForLiveProcess(llvm::StringRef plugin_name, Process &process) { 80 if (!process.IsLiveDebugSession()) 81 return createStringError(inconvertibleErrorCode(), 82 "Can't trace non-live processes"); 83 84 ConstString name(plugin_name); 85 if (auto create_callback = 86 PluginManager::GetTraceCreateCallbackForLiveProcess(name)) 87 return create_callback(process); 88 89 return createInvalidPlugInError(plugin_name); 90 } 91 92 Expected<StringRef> Trace::FindPluginSchema(StringRef name) { 93 ConstString plugin_name(name); 94 StringRef schema = PluginManager::GetTraceSchema(plugin_name); 95 if (!schema.empty()) 96 return schema; 97 98 return createInvalidPlugInError(name); 99 } 100 101 static int GetNumberOfDigits(size_t num) { 102 return num == 0 ? 1 : static_cast<int>(log10(num)) + 1; 103 } 104 105 /// \return 106 /// \b true if the provided line entries match line, column and source file. 107 /// This function assumes that the line entries are valid. 108 static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) { 109 if (a.line != b.line) 110 return false; 111 if (a.column != b.column) 112 return false; 113 return a.file == b.file; 114 } 115 116 // This custom LineEntry validator is neded because some line_entries have 117 // 0 as line, which is meaningless. Notice that LineEntry::IsValid only 118 // checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX. 119 static bool IsLineEntryValid(const LineEntry &line_entry) { 120 return line_entry.IsValid() && line_entry.line > 0; 121 } 122 123 /// Helper structure for \a TraverseInstructionsWithSymbolInfo. 124 struct InstructionSymbolInfo { 125 SymbolContext sc; 126 Address address; 127 lldb::addr_t load_address; 128 lldb::DisassemblerSP disassembler; 129 lldb::InstructionSP instruction; 130 lldb_private::ExecutionContext exe_ctx; 131 }; 132 133 /// InstructionSymbolInfo object with symbol information for the given 134 /// instruction, calculated efficiently. 135 /// 136 /// \param[in] symbol_scope 137 /// If not \b 0, then the \a InstructionSymbolInfo will have its 138 /// SymbolContext calculated up to that level. 139 /// 140 /// \param[in] include_disassembler 141 /// If \b true, then the \a InstructionSymbolInfo will have the 142 /// \a disassembler and \a instruction objects calculated. 143 static void TraverseInstructionsWithSymbolInfo( 144 Trace &trace, Thread &thread, size_t position, 145 Trace::TraceDirection direction, SymbolContextItem symbol_scope, 146 bool include_disassembler, 147 std::function<bool(size_t index, Expected<InstructionSymbolInfo> insn)> 148 callback) { 149 InstructionSymbolInfo prev_insn; 150 151 Target &target = thread.GetProcess()->GetTarget(); 152 ExecutionContext exe_ctx; 153 target.CalculateExecutionContext(exe_ctx); 154 const ArchSpec &arch = target.GetArchitecture(); 155 156 // Find the symbol context for the given address reusing the previous 157 // instruction's symbol context when possible. 158 auto calculate_symbol_context = [&](const Address &address) { 159 AddressRange range; 160 if (prev_insn.sc.GetAddressRange(symbol_scope, 0, 161 /*inline_block_range*/ false, range) && 162 range.Contains(address)) 163 return prev_insn.sc; 164 165 SymbolContext sc; 166 address.CalculateSymbolContext(&sc, symbol_scope); 167 return sc; 168 }; 169 170 // Find the disassembler for the given address reusing the previous 171 // instruction's disassembler when possible. 172 auto calculate_disass = [&](const Address &address, const SymbolContext &sc) { 173 if (prev_insn.disassembler) { 174 if (InstructionSP instruction = 175 prev_insn.disassembler->GetInstructionList() 176 .GetInstructionAtAddress(address)) 177 return std::make_tuple(prev_insn.disassembler, instruction); 178 } 179 180 if (sc.function) { 181 if (DisassemblerSP disassembler = 182 sc.function->GetInstructions(exe_ctx, nullptr)) { 183 if (InstructionSP instruction = 184 disassembler->GetInstructionList().GetInstructionAtAddress( 185 address)) 186 return std::make_tuple(disassembler, instruction); 187 } 188 } 189 // We fallback to a single instruction disassembler 190 AddressRange range(address, arch.GetMaximumOpcodeByteSize()); 191 DisassemblerSP disassembler = 192 Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, 193 /*flavor*/ nullptr, target, range); 194 return std::make_tuple(disassembler, 195 disassembler ? disassembler->GetInstructionList() 196 .GetInstructionAtAddress(address) 197 : InstructionSP()); 198 }; 199 200 trace.TraverseInstructions( 201 thread, position, direction, 202 [&](size_t index, Expected<lldb::addr_t> load_address) -> bool { 203 if (!load_address) 204 return callback(index, load_address.takeError()); 205 206 InstructionSymbolInfo insn; 207 insn.load_address = *load_address; 208 insn.exe_ctx = exe_ctx; 209 insn.address.SetLoadAddress(*load_address, &target); 210 if (symbol_scope != 0) 211 insn.sc = calculate_symbol_context(insn.address); 212 if (include_disassembler) 213 std::tie(insn.disassembler, insn.instruction) = 214 calculate_disass(insn.address, insn.sc); 215 prev_insn = insn; 216 return callback(index, insn); 217 }); 218 } 219 220 /// Compare the symbol contexts of the provided \a InstructionSymbolInfo 221 /// objects. 222 /// 223 /// \return 224 /// \a true if both instructions belong to the same scope level analized 225 /// in the following order: 226 /// - module 227 /// - symbol 228 /// - function 229 /// - line 230 static bool 231 IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn, 232 const InstructionSymbolInfo &insn) { 233 // module checks 234 if (insn.sc.module_sp != prev_insn.sc.module_sp) 235 return false; 236 237 // symbol checks 238 if (insn.sc.symbol != prev_insn.sc.symbol) 239 return false; 240 241 // function checks 242 if (!insn.sc.function && !prev_insn.sc.function) 243 return true; 244 else if (insn.sc.function != prev_insn.sc.function) 245 return false; 246 247 // line entry checks 248 const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry); 249 const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry); 250 if (curr_line_valid && prev_line_valid) 251 return FileLineAndColumnMatches(insn.sc.line_entry, 252 prev_insn.sc.line_entry); 253 return curr_line_valid == prev_line_valid; 254 } 255 256 /// Dump the symbol context of the given instruction address if it's different 257 /// from the symbol context of the previous instruction in the trace. 258 /// 259 /// \param[in] prev_sc 260 /// The symbol context of the previous instruction in the trace. 261 /// 262 /// \param[in] address 263 /// The address whose symbol information will be dumped. 264 /// 265 /// \return 266 /// The symbol context of the current address, which might differ from the 267 /// previous one. 268 static void 269 DumpInstructionSymbolContext(Stream &s, 270 Optional<InstructionSymbolInfo> prev_insn, 271 InstructionSymbolInfo &insn) { 272 if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn)) 273 return; 274 275 s.Printf(" "); 276 277 if (!insn.sc.module_sp) 278 s.Printf("(none)"); 279 else if (!insn.sc.function && !insn.sc.symbol) 280 s.Printf("%s`(none)", 281 insn.sc.module_sp->GetFileSpec().GetFilename().AsCString()); 282 else 283 insn.sc.DumpStopContext(&s, insn.exe_ctx.GetTargetPtr(), insn.address, 284 /*show_fullpath*/ false, 285 /*show_module*/ true, /*show_inlined_frames*/ false, 286 /*show_function_arguments*/ true, 287 /*show_function_name*/ true); 288 s.Printf("\n"); 289 } 290 291 static void DumpInstructionDisassembly(Stream &s, InstructionSymbolInfo &insn) { 292 if (!insn.instruction) 293 return; 294 s.Printf(" "); 295 insn.instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, 296 /*max_opcode_byte_size*/ 0, &insn.exe_ctx, &insn.sc, 297 /*prev_sym_ctx*/ nullptr, 298 /*disassembly_addr_format*/ nullptr, 299 /*max_address_text_size*/ 0); 300 } 301 302 void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, 303 size_t end_position, bool raw) { 304 Optional<size_t> instructions_count = GetInstructionCount(thread); 305 if (!instructions_count) { 306 s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), 307 thread.GetID()); 308 return; 309 } 310 311 s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", 312 thread.GetIndexID(), thread.GetID(), *instructions_count); 313 314 if (count == 0 || end_position >= *instructions_count) 315 return; 316 317 int digits_count = GetNumberOfDigits(end_position); 318 size_t start_position = 319 end_position + 1 < count ? 0 : end_position + 1 - count; 320 auto printInstructionIndex = [&](size_t index) { 321 s.Printf(" [%*zu] ", digits_count, index); 322 }; 323 324 bool was_prev_instruction_an_error = false; 325 Optional<InstructionSymbolInfo> prev_insn; 326 327 TraverseInstructionsWithSymbolInfo( 328 *this, thread, start_position, TraceDirection::Forwards, 329 eSymbolContextEverything, /*disassembler*/ true, 330 [&](size_t index, Expected<InstructionSymbolInfo> insn) -> bool { 331 if (!insn) { 332 printInstructionIndex(index); 333 s << toString(insn.takeError()); 334 335 prev_insn = None; 336 was_prev_instruction_an_error = true; 337 } else { 338 if (was_prev_instruction_an_error) 339 s.Printf(" ...missing instructions\n"); 340 341 if (!raw) 342 DumpInstructionSymbolContext(s, prev_insn, *insn); 343 344 printInstructionIndex(index); 345 s.Printf("0x%016" PRIx64, insn->load_address); 346 347 if (!raw) 348 DumpInstructionDisassembly(s, *insn); 349 350 prev_insn = *insn; 351 was_prev_instruction_an_error = false; 352 } 353 354 s.Printf("\n"); 355 return index < end_position; 356 }); 357 } 358 359 Error Trace::Start(const llvm::json::Value &request) { 360 if (!m_live_process) 361 return createStringError(inconvertibleErrorCode(), 362 "Tracing requires a live process."); 363 return m_live_process->TraceStart(request); 364 } 365 366 Error Trace::StopProcess() { 367 if (!m_live_process) 368 return createStringError(inconvertibleErrorCode(), 369 "Tracing requires a live process."); 370 return m_live_process->TraceStop( 371 TraceStopRequest(GetPluginName().AsCString())); 372 } 373 374 Error Trace::StopThreads(const std::vector<lldb::tid_t> &tids) { 375 if (!m_live_process) 376 return createStringError(inconvertibleErrorCode(), 377 "Tracing requires a live process."); 378 return m_live_process->TraceStop( 379 TraceStopRequest(GetPluginName().AsCString(), tids)); 380 } 381 382 Expected<std::string> Trace::GetLiveProcessState() { 383 if (!m_live_process) 384 return createStringError(inconvertibleErrorCode(), 385 "Tracing requires a live process."); 386 return m_live_process->TraceGetState(GetPluginName().AsCString()); 387 } 388 389 Optional<size_t> Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, 390 llvm::StringRef kind) { 391 auto it = m_live_thread_data.find(tid); 392 if (it == m_live_thread_data.end()) 393 return None; 394 std::unordered_map<std::string, size_t> &single_thread_data = it->second; 395 auto single_thread_data_it = single_thread_data.find(kind.str()); 396 if (single_thread_data_it == single_thread_data.end()) 397 return None; 398 return single_thread_data_it->second; 399 } 400 401 Optional<size_t> Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { 402 auto data_it = m_live_process_data.find(kind.str()); 403 if (data_it == m_live_process_data.end()) 404 return None; 405 return data_it->second; 406 } 407 408 Expected<std::vector<uint8_t>> 409 Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { 410 if (!m_live_process) 411 return createStringError(inconvertibleErrorCode(), 412 "Tracing requires a live process."); 413 llvm::Optional<size_t> size = GetLiveThreadBinaryDataSize(tid, kind); 414 if (!size) 415 return createStringError( 416 inconvertibleErrorCode(), 417 "Tracing data \"%s\" is not available for thread %" PRIu64 ".", 418 kind.data(), tid); 419 420 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 421 static_cast<int64_t>(tid), 0, 422 static_cast<int64_t>(*size)}; 423 return m_live_process->TraceGetBinaryData(request); 424 } 425 426 Expected<std::vector<uint8_t>> 427 Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { 428 if (!m_live_process) 429 return createStringError(inconvertibleErrorCode(), 430 "Tracing requires a live process."); 431 llvm::Optional<size_t> size = GetLiveProcessBinaryDataSize(kind); 432 if (!size) 433 return createStringError( 434 inconvertibleErrorCode(), 435 "Tracing data \"%s\" is not available for the process.", kind.data()); 436 437 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 438 None, 0, static_cast<int64_t>(*size)}; 439 return m_live_process->TraceGetBinaryData(request); 440 } 441 442 void Trace::RefreshLiveProcessState() { 443 if (!m_live_process) 444 return; 445 446 uint32_t new_stop_id = m_live_process->GetStopID(); 447 if (new_stop_id == m_stop_id) 448 return; 449 450 m_stop_id = new_stop_id; 451 m_live_thread_data.clear(); 452 453 Expected<std::string> json_string = GetLiveProcessState(); 454 if (!json_string) { 455 DoRefreshLiveProcessState(json_string.takeError()); 456 return; 457 } 458 Expected<TraceGetStateResponse> live_process_state = 459 json::parse<TraceGetStateResponse>(*json_string, "TraceGetStateResponse"); 460 if (!live_process_state) { 461 DoRefreshLiveProcessState(live_process_state.takeError()); 462 return; 463 } 464 465 for (const TraceThreadState &thread_state : 466 live_process_state->tracedThreads) { 467 for (const TraceBinaryData &item : thread_state.binaryData) 468 m_live_thread_data[thread_state.tid][item.kind] = item.size; 469 } 470 471 for (const TraceBinaryData &item : live_process_state->processBinaryData) 472 m_live_process_data[item.kind] = item.size; 473 474 DoRefreshLiveProcessState(std::move(live_process_state)); 475 } 476