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