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.
ToOptionalString(const char * s)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.
GetModuleName(const TraceDumper::TraceItem & item)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.
IsLineEntryValid(const LineEntry & line_entry)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.
FileLineAndColumnMatches(const LineEntry & a,const LineEntry & b)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
IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo & prev_insn,const TraceDumper::SymbolInfo & insn)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:
OutputWriterCLI(Stream & s,const TraceDumperOptions & options,Thread & thread)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
NoMoreData()101 void NoMoreData() override { m_s << " no more data\n"; }
102
TraceItem(const TraceDumper::TraceItem & item)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:
OutputWriterJSON(Stream & s,const TraceDumperOptions & options)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
~OutputWriterJSON()212 ~OutputWriterJSON() { m_j.arrayEnd(); }
213
DumpEvent(const TraceDumper::TraceItem & item)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
DumpInstruction(const TraceDumper::TraceItem & item)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
TraceItem(const TraceDumper::TraceItem & item)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>
CreateWriter(Stream & s,const TraceDumperOptions & options,Thread & thread)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
TraceDumper(lldb::TraceCursorUP && cursor_up,Stream & s,const TraceDumperOptions & options)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
CreatRawTraceItem()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
CalculateSymbolContext(const Address & address,const TraceDumper::SymbolInfo & prev_symbol_info)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>
CalculateDisass(const TraceDumper::SymbolInfo & symbol_info,const TraceDumper::SymbolInfo & prev_symbol_info,const ExecutionContext & exe_ctx)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
DumpInstructions(size_t count)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