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 /*show_inline_callsite_line_info*/ false); 146 s.Printf("\n"); 147 return sc; 148 } 149 150 /// Dump an instruction given by its address using a given disassembler, unless 151 /// the instruction is not present in the disassembler. 152 /// 153 /// \param[in] disassembler 154 /// A disassembler containing a certain instruction list. 155 /// 156 /// \param[in] address 157 /// The address of the instruction to dump. 158 /// 159 /// \return 160 /// \b true if the information could be dumped, \b false otherwise. 161 static bool TryDumpInstructionInfo(Stream &s, 162 const DisassemblerSP &disassembler, 163 const ExecutionContext &exe_ctx, 164 const Address &address) { 165 if (!disassembler) 166 return false; 167 168 if (InstructionSP instruction = 169 disassembler->GetInstructionList().GetInstructionAtAddress(address)) { 170 instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, 171 /*max_opcode_byte_size*/ 0, &exe_ctx, 172 /*sym_ctx*/ nullptr, /*prev_sym_ctx*/ nullptr, 173 /*disassembly_addr_format*/ nullptr, 174 /*max_address_text_size*/ 0); 175 return true; 176 } 177 178 return false; 179 } 180 181 /// Dump an instruction instruction given by its address. 182 /// 183 /// \param[in] prev_disassembler 184 /// The disassembler that was used to dump the previous instruction in the 185 /// trace. It is useful to avoid recomputations. 186 /// 187 /// \param[in] address 188 /// The address of the instruction to dump. 189 /// 190 /// \return 191 /// A disassembler that contains the given instruction, which might differ 192 /// from the previous disassembler. 193 static DisassemblerSP 194 DumpInstructionInfo(Stream &s, const SymbolContext &sc, 195 const DisassemblerSP &prev_disassembler, 196 ExecutionContext &exe_ctx, const Address &address) { 197 // We first try to use the previous disassembler 198 if (TryDumpInstructionInfo(s, prev_disassembler, exe_ctx, address)) 199 return prev_disassembler; 200 201 // Now we try using the current function's disassembler 202 if (sc.function) { 203 DisassemblerSP disassembler = 204 sc.function->GetInstructions(exe_ctx, nullptr, true); 205 if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address)) 206 return disassembler; 207 } 208 209 // We fallback to disassembly one instruction 210 Target &target = exe_ctx.GetTargetRef(); 211 const ArchSpec &arch = target.GetArchitecture(); 212 AddressRange range(address, arch.GetMaximumOpcodeByteSize() * 1); 213 DisassemblerSP disassembler = Disassembler::DisassembleRange( 214 arch, /*plugin_name*/ nullptr, 215 /*flavor*/ nullptr, target, range, /*prefer_file_cache*/ true); 216 if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address)) 217 return disassembler; 218 return nullptr; 219 } 220 221 void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, 222 size_t end_position, bool raw) { 223 if (!IsTraced(thread)) { 224 s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), 225 thread.GetID()); 226 return; 227 } 228 229 size_t instructions_count = GetInstructionCount(thread); 230 s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", 231 thread.GetIndexID(), thread.GetID(), instructions_count); 232 233 if (count == 0 || end_position >= instructions_count) 234 return; 235 236 size_t start_position = 237 end_position + 1 < count ? 0 : end_position + 1 - count; 238 239 int digits_count = GetNumberOfDigits(end_position); 240 auto printInstructionIndex = [&](size_t index) { 241 s.Printf(" [%*zu] ", digits_count, index); 242 }; 243 244 bool was_prev_instruction_an_error = false; 245 Target &target = thread.GetProcess()->GetTarget(); 246 247 SymbolContext sc; 248 DisassemblerSP disassembler; 249 ExecutionContext exe_ctx; 250 target.CalculateExecutionContext(exe_ctx); 251 252 TraverseInstructions( 253 thread, start_position, TraceDirection::Forwards, 254 [&](size_t index, Expected<lldb::addr_t> load_address) -> bool { 255 if (load_address) { 256 // We print an empty line after a sequence of errors to show more 257 // clearly that there's a gap in the trace 258 if (was_prev_instruction_an_error) 259 s.Printf(" ...missing instructions\n"); 260 261 Address address; 262 if (!raw) { 263 target.GetSectionLoadList().ResolveLoadAddress(*load_address, 264 address); 265 266 sc = DumpSymbolContext(s, sc, target, address); 267 } 268 269 printInstructionIndex(index); 270 s.Printf("0x%016" PRIx64 " ", *load_address); 271 272 if (!raw) { 273 disassembler = 274 DumpInstructionInfo(s, sc, disassembler, exe_ctx, address); 275 } 276 277 was_prev_instruction_an_error = false; 278 } else { 279 printInstructionIndex(index); 280 s << toString(load_address.takeError()); 281 was_prev_instruction_an_error = true; 282 if (!raw) 283 sc = SymbolContext(); 284 } 285 286 s.Printf("\n"); 287 288 return index < end_position; 289 }); 290 } 291 292 Error Trace::Start(const llvm::json::Value &request) { 293 if (!m_live_process) 294 return createStringError(inconvertibleErrorCode(), 295 "Tracing requires a live process."); 296 return m_live_process->TraceStart(request); 297 } 298 299 Error Trace::StopProcess() { 300 if (!m_live_process) 301 return createStringError(inconvertibleErrorCode(), 302 "Tracing requires a live process."); 303 return m_live_process->TraceStop( 304 TraceStopRequest(GetPluginName().AsCString())); 305 } 306 307 Error Trace::StopThreads(const std::vector<lldb::tid_t> &tids) { 308 if (!m_live_process) 309 return createStringError(inconvertibleErrorCode(), 310 "Tracing requires a live process."); 311 return m_live_process->TraceStop( 312 TraceStopRequest(GetPluginName().AsCString(), tids)); 313 } 314 315 Expected<std::string> Trace::GetLiveProcessState() { 316 if (!m_live_process) 317 return createStringError(inconvertibleErrorCode(), 318 "Tracing requires a live process."); 319 return m_live_process->TraceGetState(GetPluginName().AsCString()); 320 } 321 322 Optional<size_t> Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, 323 llvm::StringRef kind) { 324 auto it = m_live_thread_data.find(tid); 325 if (it == m_live_thread_data.end()) 326 return None; 327 std::unordered_map<std::string, size_t> &single_thread_data = it->second; 328 auto single_thread_data_it = single_thread_data.find(kind.str()); 329 if (single_thread_data_it == single_thread_data.end()) 330 return None; 331 return single_thread_data_it->second; 332 } 333 334 Optional<size_t> Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { 335 auto data_it = m_live_process_data.find(kind.str()); 336 if (data_it == m_live_process_data.end()) 337 return None; 338 return data_it->second; 339 } 340 341 Expected<std::vector<uint8_t>> 342 Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { 343 if (!m_live_process) 344 return createStringError(inconvertibleErrorCode(), 345 "Tracing requires a live process."); 346 llvm::Optional<size_t> size = GetLiveThreadBinaryDataSize(tid, kind); 347 if (!size) 348 return createStringError( 349 inconvertibleErrorCode(), 350 "Tracing data \"%s\" is not available for thread %" PRIu64 ".", 351 kind.data(), tid); 352 353 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 354 static_cast<int64_t>(tid), 0, 355 static_cast<int64_t>(*size)}; 356 return m_live_process->TraceGetBinaryData(request); 357 } 358 359 Expected<std::vector<uint8_t>> 360 Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { 361 if (!m_live_process) 362 return createStringError(inconvertibleErrorCode(), 363 "Tracing requires a live process."); 364 llvm::Optional<size_t> size = GetLiveProcessBinaryDataSize(kind); 365 if (!size) 366 return createStringError( 367 inconvertibleErrorCode(), 368 "Tracing data \"%s\" is not available for the process.", kind.data()); 369 370 TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), 371 None, 0, static_cast<int64_t>(*size)}; 372 return m_live_process->TraceGetBinaryData(request); 373 } 374 375 void Trace::RefreshLiveProcessState() { 376 if (!m_live_process) 377 return; 378 379 uint32_t new_stop_id = m_live_process->GetStopID(); 380 if (new_stop_id == m_stop_id) 381 return; 382 383 m_stop_id = new_stop_id; 384 m_live_thread_data.clear(); 385 386 Expected<std::string> json_string = GetLiveProcessState(); 387 if (!json_string) { 388 DoRefreshLiveProcessState(json_string.takeError()); 389 return; 390 } 391 Expected<TraceGetStateResponse> live_process_state = 392 json::parse<TraceGetStateResponse>(*json_string, "TraceGetStateResponse"); 393 if (!live_process_state) { 394 DoRefreshLiveProcessState(live_process_state.takeError()); 395 return; 396 } 397 398 for (const TraceThreadState &thread_state : 399 live_process_state->tracedThreads) { 400 for (const TraceBinaryData &item : thread_state.binaryData) 401 m_live_thread_data[thread_state.tid][item.kind] = item.size; 402 } 403 404 for (const TraceBinaryData &item : live_process_state->processBinaryData) 405 m_live_process_data[item.kind] = item.size; 406 407 DoRefreshLiveProcessState(std::move(live_process_state)); 408 } 409