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 insn.instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, 295 /*max_opcode_byte_size*/ 0, &insn.exe_ctx, &insn.sc, 296 /*prev_sym_ctx*/ nullptr, 297 /*disassembly_addr_format*/ nullptr, 298 /*max_address_text_size*/ 0); 299 } 300 301 void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, 302 size_t end_position, bool raw) { 303 Optional<size_t> instructions_count = GetInstructionCount(thread); 304 if (!instructions_count) { 305 s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), 306 thread.GetID()); 307 return; 308 } 309 310 s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", 311 thread.GetIndexID(), thread.GetID(), *instructions_count); 312 313 if (count == 0 || end_position >= *instructions_count) 314 return; 315 316 int digits_count = GetNumberOfDigits(end_position); 317 size_t start_position = 318 end_position + 1 < count ? 0 : end_position + 1 - count; 319 auto printInstructionIndex = [&](size_t index) { 320 s.Printf(" [%*zu] ", digits_count, index); 321 }; 322 323 bool was_prev_instruction_an_error = false; 324 Optional<InstructionSymbolInfo> prev_insn; 325 326 TraverseInstructionsWithSymbolInfo( 327 *this, thread, start_position, TraceDirection::Forwards, 328 eSymbolContextEverything, /*disassembler*/ true, 329 [&](size_t index, Expected<InstructionSymbolInfo> insn) -> bool { 330 if (!insn) { 331 printInstructionIndex(index); 332 s << toString(insn.takeError()); 333 334 prev_insn = None; 335 was_prev_instruction_an_error = true; 336 } else { 337 if (was_prev_instruction_an_error) 338 s.Printf(" ...missing instructions\n"); 339 340 if (!raw) 341 DumpInstructionSymbolContext(s, prev_insn, *insn); 342 343 printInstructionIndex(index); 344 s.Printf("0x%016" PRIx64 " ", insn->load_address); 345 346 if (!raw) 347 DumpInstructionDisassembly(s, *insn); 348 349 prev_insn = *insn; 350 was_prev_instruction_an_error = false; 351 } 352 353 s.Printf("\n"); 354 return index < end_position; 355 }); 356 } 357 358 Error Trace::Start(const llvm::json::Value &request) { 359 if (!m_live_process) 360 return createStringError(inconvertibleErrorCode(), 361 "Tracing requires a live process."); 362 return m_live_process->TraceStart(request); 363 } 364 365 Error Trace::StopProcess() { 366 if (!m_live_process) 367 return createStringError(inconvertibleErrorCode(), 368 "Tracing requires a live process."); 369 return m_live_process->TraceStop( 370 TraceStopRequest(GetPluginName().AsCString())); 371 } 372 373 Error Trace::StopThreads(const std::vector<lldb::tid_t> &tids) { 374 if (!m_live_process) 375 return createStringError(inconvertibleErrorCode(), 376 "Tracing requires a live process."); 377 return m_live_process->TraceStop( 378 TraceStopRequest(GetPluginName().AsCString(), tids)); 379 } 380 381 Expected<std::string> Trace::GetLiveProcessState() { 382 if (!m_live_process) 383 return createStringError(inconvertibleErrorCode(), 384 "Tracing requires a live process."); 385 return m_live_process->TraceGetState(GetPluginName().AsCString()); 386 } 387 388 Optional<size_t> Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, 389 llvm::StringRef kind) { 390 auto it = m_live_thread_data.find(tid); 391 if (it == m_live_thread_data.end()) 392 return None; 393 std::unordered_map<std::string, size_t> &single_thread_data = it->second; 394 auto single_thread_data_it = single_thread_data.find(kind.str()); 395 if (single_thread_data_it == single_thread_data.end()) 396 return None; 397 return single_thread_data_it->second; 398 } 399 400 Optional<size_t> Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { 401 auto data_it = m_live_process_data.find(kind.str()); 402 if (data_it == m_live_process_data.end()) 403 return None; 404 return data_it->second; 405 } 406 407 Expected<std::vector<uint8_t>> 408 Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { 409 if (!m_live_process) 410 return createStringError(inconvertibleErrorCode(), 411 "Tracing requires a live process."); 412 llvm::Optional<size_t> size = GetLiveThreadBinaryDataSize(tid, kind); 413 if (!size) 414 return createStringError( 415 inconvertibleErrorCode(), 416 "Tracing data \"%s\" is not available for thread %" PRIu64 ".", 417 kind.data(), tid); 418 419 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 420 static_cast<int64_t>(tid), 0, 421 static_cast<int64_t>(*size)}; 422 return m_live_process->TraceGetBinaryData(request); 423 } 424 425 Expected<std::vector<uint8_t>> 426 Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { 427 if (!m_live_process) 428 return createStringError(inconvertibleErrorCode(), 429 "Tracing requires a live process."); 430 llvm::Optional<size_t> size = GetLiveProcessBinaryDataSize(kind); 431 if (!size) 432 return createStringError( 433 inconvertibleErrorCode(), 434 "Tracing data \"%s\" is not available for the process.", kind.data()); 435 436 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 437 None, 0, static_cast<int64_t>(*size)}; 438 return m_live_process->TraceGetBinaryData(request); 439 } 440 441 void Trace::RefreshLiveProcessState() { 442 if (!m_live_process) 443 return; 444 445 uint32_t new_stop_id = m_live_process->GetStopID(); 446 if (new_stop_id == m_stop_id) 447 return; 448 449 m_stop_id = new_stop_id; 450 m_live_thread_data.clear(); 451 452 Expected<std::string> json_string = GetLiveProcessState(); 453 if (!json_string) { 454 DoRefreshLiveProcessState(json_string.takeError()); 455 return; 456 } 457 Expected<TraceGetStateResponse> live_process_state = 458 json::parse<TraceGetStateResponse>(*json_string, "TraceGetStateResponse"); 459 if (!live_process_state) { 460 DoRefreshLiveProcessState(live_process_state.takeError()); 461 return; 462 } 463 464 for (const TraceThreadState &thread_state : 465 live_process_state->tracedThreads) { 466 for (const TraceBinaryData &item : thread_state.binaryData) 467 m_live_thread_data[thread_state.tid][item.kind] = item.size; 468 } 469 470 for (const TraceBinaryData &item : live_process_state->processBinaryData) 471 m_live_process_data[item.kind] = item.size; 472 473 DoRefreshLiveProcessState(std::move(live_process_state)); 474 } 475