1 //===-- TraceDumper.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/TraceDumper.h" 10 11 #include "lldb/Core/Module.h" 12 #include "lldb/Symbol/CompileUnit.h" 13 #include "lldb/Symbol/Function.h" 14 #include "lldb/Target/ExecutionContext.h" 15 #include "lldb/Target/Process.h" 16 #include "lldb/Target/SectionLoadList.h" 17 18 using namespace lldb; 19 using namespace lldb_private; 20 using namespace llvm; 21 22 /// \return 23 /// The given string or \b None if it's empty. 24 static Optional<const char *> ToOptionalString(const char *s) { 25 if (!s) 26 return None; 27 return s; 28 } 29 /// \return 30 /// The module name (basename if the module is a file, or the actual name if 31 /// it's a virtual module), or \b nullptr if no name nor module was found. 32 static const char *GetModuleName(const TraceDumper::TraceItem &item) { 33 if (!item.symbol_info || !item.symbol_info->sc.module_sp) 34 return nullptr; 35 return item.symbol_info->sc.module_sp->GetFileSpec() 36 .GetFilename() 37 .AsCString(); 38 } 39 40 // This custom LineEntry validator is neded because some line_entries have 41 // 0 as line, which is meaningless. Notice that LineEntry::IsValid only 42 // checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX. 43 static bool IsLineEntryValid(const LineEntry &line_entry) { 44 return line_entry.IsValid() && line_entry.line > 0; 45 } 46 47 /// \return 48 /// \b true if the provided line entries match line, column and source file. 49 /// This function assumes that the line entries are valid. 50 static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) { 51 if (a.line != b.line) 52 return false; 53 if (a.column != b.column) 54 return false; 55 return a.file == b.file; 56 } 57 58 /// Compare the symbol contexts of the provided \a SymbolInfo 59 /// objects. 60 /// 61 /// \return 62 /// \a true if both instructions belong to the same scope level analized 63 /// in the following order: 64 /// - module 65 /// - symbol 66 /// - function 67 /// - line 68 static bool 69 IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo &prev_insn, 70 const TraceDumper::SymbolInfo &insn) { 71 // module checks 72 if (insn.sc.module_sp != prev_insn.sc.module_sp) 73 return false; 74 75 // symbol checks 76 if (insn.sc.symbol != prev_insn.sc.symbol) 77 return false; 78 79 // function checks 80 if (!insn.sc.function && !prev_insn.sc.function) 81 return true; 82 else if (insn.sc.function != prev_insn.sc.function) 83 return false; 84 85 // line entry checks 86 const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry); 87 const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry); 88 if (curr_line_valid && prev_line_valid) 89 return FileLineAndColumnMatches(insn.sc.line_entry, 90 prev_insn.sc.line_entry); 91 return curr_line_valid == prev_line_valid; 92 } 93 94 class OutputWriterCLI : public TraceDumper::OutputWriter { 95 public: 96 OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread) 97 : m_s(s), m_options(options) { 98 m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID()); 99 }; 100 101 void NoMoreData() override { m_s << " no more data\n"; } 102 103 void TraceItem(const TraceDumper::TraceItem &item) override { 104 if (item.symbol_info) { 105 if (!item.prev_symbol_info || 106 !IsSameInstructionSymbolContext(*item.prev_symbol_info, 107 *item.symbol_info)) { 108 m_s << " "; 109 const char *module_name = GetModuleName(item); 110 if (!module_name) 111 m_s << "(none)"; 112 else if (!item.symbol_info->sc.function && !item.symbol_info->sc.symbol) 113 m_s.Format("{0}`(none)", module_name); 114 else 115 item.symbol_info->sc.DumpStopContext( 116 &m_s, item.symbol_info->exe_ctx.GetTargetPtr(), 117 item.symbol_info->address, 118 /*show_fullpaths=*/false, 119 /*show_module=*/true, /*show_inlined_frames=*/false, 120 /*show_function_arguments=*/true, 121 /*show_function_name=*/true); 122 m_s << "\n"; 123 } 124 } 125 126 if (item.error && !m_was_prev_instruction_an_error) 127 m_s << " ...missing instructions\n"; 128 129 m_s.Format(" {0}: ", item.id); 130 131 if (m_options.show_timestamps) { 132 m_s.Format("[{0}] ", item.timestamp 133 ? formatv("{0:3} ns", *item.timestamp).str() 134 : "unavailable"); 135 } 136 137 if (item.event) { 138 m_s << "(event) " << TraceCursor::EventKindToString(*item.event); 139 switch (*item.event) { 140 case eTraceEventCPUChanged: 141 m_s.Format(" [new CPU={0}]", 142 item.cpu_id ? std::to_string(*item.cpu_id) : "unavailable"); 143 break; 144 case eTraceEventHWClockTick: 145 m_s.Format(" [{0}]", item.hw_clock ? std::to_string(*item.hw_clock) 146 : "unavailable"); 147 break; 148 case eTraceEventDisabledHW: 149 case eTraceEventDisabledSW: 150 break; 151 } 152 } else if (item.error) { 153 m_s << "(error) " << *item.error; 154 } else { 155 m_s.Format("{0:x+16}", item.load_address); 156 if (item.symbol_info && item.symbol_info->instruction) { 157 m_s << " "; 158 item.symbol_info->instruction->Dump( 159 &m_s, /*max_opcode_byte_size=*/0, 160 /*show_address=*/false, 161 /*show_bytes=*/false, m_options.show_control_flow_kind, 162 &item.symbol_info->exe_ctx, &item.symbol_info->sc, 163 /*prev_sym_ctx=*/nullptr, 164 /*disassembly_addr_format=*/nullptr, 165 /*max_address_text_size=*/0); 166 } 167 } 168 169 m_was_prev_instruction_an_error = (bool)item.error; 170 m_s << "\n"; 171 } 172 173 private: 174 Stream &m_s; 175 TraceDumperOptions m_options; 176 bool m_was_prev_instruction_an_error = false; 177 }; 178 179 class OutputWriterJSON : public TraceDumper::OutputWriter { 180 /* schema: 181 error_message: string 182 | { 183 "event": string, 184 "id": decimal, 185 "tsc"?: string decimal, 186 "cpuId"? decimal, 187 } | { 188 "error": string, 189 "id": decimal, 190 "tsc"?: string decimal, 191 | { 192 "loadAddress": string decimal, 193 "id": decimal, 194 "hwClock"?: string decimal, 195 "timestamp_ns"?: string decimal, 196 "module"?: string, 197 "symbol"?: string, 198 "line"?: decimal, 199 "column"?: decimal, 200 "source"?: string, 201 "mnemonic"?: string, 202 } 203 */ 204 public: 205 OutputWriterJSON(Stream &s, const TraceDumperOptions &options) 206 : m_s(s), m_options(options), 207 m_j(m_s.AsRawOstream(), 208 /*IndentSize=*/options.pretty_print_json ? 2 : 0) { 209 m_j.arrayBegin(); 210 }; 211 212 ~OutputWriterJSON() { m_j.arrayEnd(); } 213 214 void DumpEvent(const TraceDumper::TraceItem &item) { 215 m_j.attribute("event", TraceCursor::EventKindToString(*item.event)); 216 switch (*item.event) { 217 case eTraceEventCPUChanged: 218 m_j.attribute("cpuId", item.cpu_id); 219 break; 220 case eTraceEventHWClockTick: 221 m_j.attribute("hwClock", item.hw_clock); 222 break; 223 case eTraceEventDisabledHW: 224 case eTraceEventDisabledSW: 225 break; 226 } 227 } 228 229 void DumpInstruction(const TraceDumper::TraceItem &item) { 230 m_j.attribute("loadAddress", formatv("{0:x}", item.load_address)); 231 if (item.symbol_info) { 232 m_j.attribute("module", ToOptionalString(GetModuleName(item))); 233 m_j.attribute( 234 "symbol", 235 ToOptionalString(item.symbol_info->sc.GetFunctionName().AsCString())); 236 237 if (item.symbol_info->instruction) { 238 m_j.attribute("mnemonic", 239 ToOptionalString(item.symbol_info->instruction->GetMnemonic( 240 &item.symbol_info->exe_ctx))); 241 } 242 243 if (IsLineEntryValid(item.symbol_info->sc.line_entry)) { 244 m_j.attribute( 245 "source", 246 ToOptionalString( 247 item.symbol_info->sc.line_entry.file.GetPath().c_str())); 248 m_j.attribute("line", item.symbol_info->sc.line_entry.line); 249 m_j.attribute("column", item.symbol_info->sc.line_entry.column); 250 } 251 } 252 } 253 254 void TraceItem(const TraceDumper::TraceItem &item) override { 255 m_j.object([&] { 256 m_j.attribute("id", item.id); 257 if (m_options.show_timestamps) 258 m_j.attribute("timestamp_ns", item.timestamp 259 ? Optional<std::string>( 260 std::to_string(*item.timestamp)) 261 : None); 262 263 if (item.event) { 264 DumpEvent(item); 265 } else if (item.error) { 266 m_j.attribute("error", *item.error); 267 } else { 268 DumpInstruction(item); 269 } 270 }); 271 } 272 273 private: 274 Stream &m_s; 275 TraceDumperOptions m_options; 276 json::OStream m_j; 277 }; 278 279 static std::unique_ptr<TraceDumper::OutputWriter> 280 CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) { 281 if (options.json) 282 return std::unique_ptr<TraceDumper::OutputWriter>( 283 new OutputWriterJSON(s, options)); 284 else 285 return std::unique_ptr<TraceDumper::OutputWriter>( 286 new OutputWriterCLI(s, options, thread)); 287 } 288 289 TraceDumper::TraceDumper(lldb::TraceCursorUP &&cursor_up, Stream &s, 290 const TraceDumperOptions &options) 291 : m_cursor_up(std::move(cursor_up)), m_options(options), 292 m_writer_up(CreateWriter( 293 s, m_options, *m_cursor_up->GetExecutionContextRef().GetThreadSP())) { 294 295 if (m_options.id) 296 m_cursor_up->GoToId(*m_options.id); 297 else if (m_options.forwards) 298 m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning); 299 else 300 m_cursor_up->Seek(0, TraceCursor::SeekType::End); 301 302 m_cursor_up->SetForwards(m_options.forwards); 303 if (m_options.skip) { 304 m_cursor_up->Seek((m_options.forwards ? 1 : -1) * *m_options.skip, 305 TraceCursor::SeekType::Current); 306 } 307 } 308 309 TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() { 310 TraceItem item = {}; 311 item.id = m_cursor_up->GetId(); 312 313 if (m_options.show_timestamps) 314 item.timestamp = m_cursor_up->GetWallClockTime(); 315 return item; 316 } 317 318 /// Find the symbol context for the given address reusing the previous 319 /// instruction's symbol context when possible. 320 static SymbolContext 321 CalculateSymbolContext(const Address &address, 322 const TraceDumper::SymbolInfo &prev_symbol_info) { 323 AddressRange range; 324 if (prev_symbol_info.sc.GetAddressRange(eSymbolContextEverything, 0, 325 /*inline_block_range*/ false, 326 range) && 327 range.Contains(address)) 328 return prev_symbol_info.sc; 329 330 SymbolContext sc; 331 address.CalculateSymbolContext(&sc, eSymbolContextEverything); 332 return sc; 333 } 334 335 /// Find the disassembler for the given address reusing the previous 336 /// instruction's disassembler when possible. 337 static std::tuple<DisassemblerSP, InstructionSP> 338 CalculateDisass(const TraceDumper::SymbolInfo &symbol_info, 339 const TraceDumper::SymbolInfo &prev_symbol_info, 340 const ExecutionContext &exe_ctx) { 341 if (prev_symbol_info.disassembler) { 342 if (InstructionSP instruction = 343 prev_symbol_info.disassembler->GetInstructionList() 344 .GetInstructionAtAddress(symbol_info.address)) 345 return std::make_tuple(prev_symbol_info.disassembler, instruction); 346 } 347 348 if (symbol_info.sc.function) { 349 if (DisassemblerSP disassembler = 350 symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) { 351 if (InstructionSP instruction = 352 disassembler->GetInstructionList().GetInstructionAtAddress( 353 symbol_info.address)) 354 return std::make_tuple(disassembler, instruction); 355 } 356 } 357 // We fallback to a single instruction disassembler 358 Target &target = exe_ctx.GetTargetRef(); 359 const ArchSpec arch = target.GetArchitecture(); 360 AddressRange range(symbol_info.address, arch.GetMaximumOpcodeByteSize()); 361 DisassemblerSP disassembler = 362 Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, 363 /*flavor*/ nullptr, target, range); 364 return std::make_tuple( 365 disassembler, 366 disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress( 367 symbol_info.address) 368 : InstructionSP()); 369 } 370 371 Optional<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) { 372 ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP(); 373 374 SymbolInfo prev_symbol_info; 375 Optional<lldb::user_id_t> last_id; 376 377 ExecutionContext exe_ctx; 378 thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx); 379 380 for (size_t insn_seen = 0; insn_seen < count && m_cursor_up->HasValue(); 381 m_cursor_up->Next()) { 382 383 last_id = m_cursor_up->GetId(); 384 TraceItem item = CreatRawTraceItem(); 385 386 if (m_cursor_up->IsEvent()) { 387 if (!m_options.show_events) 388 continue; 389 item.event = m_cursor_up->GetEventType(); 390 switch (*item.event) { 391 case eTraceEventCPUChanged: 392 item.cpu_id = m_cursor_up->GetCPU(); 393 break; 394 case eTraceEventHWClockTick: 395 item.hw_clock = m_cursor_up->GetHWClock(); 396 break; 397 case eTraceEventDisabledHW: 398 case eTraceEventDisabledSW: 399 break; 400 } 401 } else if (m_cursor_up->IsError()) { 402 item.error = m_cursor_up->GetError(); 403 } else { 404 insn_seen++; 405 item.load_address = m_cursor_up->GetLoadAddress(); 406 407 if (!m_options.raw) { 408 SymbolInfo symbol_info; 409 symbol_info.exe_ctx = exe_ctx; 410 symbol_info.address.SetLoadAddress(item.load_address, 411 exe_ctx.GetTargetPtr()); 412 symbol_info.sc = 413 CalculateSymbolContext(symbol_info.address, prev_symbol_info); 414 std::tie(symbol_info.disassembler, symbol_info.instruction) = 415 CalculateDisass(symbol_info, prev_symbol_info, exe_ctx); 416 item.prev_symbol_info = prev_symbol_info; 417 item.symbol_info = symbol_info; 418 prev_symbol_info = symbol_info; 419 } 420 } 421 m_writer_up->TraceItem(item); 422 } 423 if (!m_cursor_up->HasValue()) 424 m_writer_up->NoMoreData(); 425 return last_id; 426 } 427