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_tsc) {
132       m_s.Format("[tsc={0}] ",
133                  item.tsc ? std::to_string(*item.tsc) : "unavailable");
134     }
135 
136     if (item.event) {
137       m_s << "(event) " << TraceCursor::EventKindToString(*item.event);
138       if (*item.event == eTraceEventCPUChanged) {
139         m_s.Format(" [new CPU={0}]",
140                    item.cpu_id ? std::to_string(*item.cpu_id) : "unavailable");
141       }
142     } else if (item.error) {
143       m_s << "(error) " << *item.error;
144     } else {
145       m_s.Format("{0:x+16}", item.load_address);
146       if (item.symbol_info && item.symbol_info->instruction) {
147         m_s << "    ";
148         item.symbol_info->instruction->Dump(
149             &m_s, /*max_opcode_byte_size=*/0,
150             /*show_address=*/false,
151             /*show_bytes=*/false, m_options.show_control_flow_kind,
152             &item.symbol_info->exe_ctx, &item.symbol_info->sc,
153             /*prev_sym_ctx=*/nullptr,
154             /*disassembly_addr_format=*/nullptr,
155             /*max_address_text_size=*/0);
156       }
157     }
158 
159     m_was_prev_instruction_an_error = (bool)item.error;
160     m_s << "\n";
161   }
162 
163 private:
164   Stream &m_s;
165   TraceDumperOptions m_options;
166   bool m_was_prev_instruction_an_error = false;
167 };
168 
169 class OutputWriterJSON : public TraceDumper::OutputWriter {
170   /* schema:
171     error_message: string
172     | {
173       "event": string,
174       "id": decimal,
175       "tsc"?: string decimal,
176       "cpuId"? decimal,
177     } | {
178       "error": string,
179       "id": decimal,
180       "tsc"?: string decimal,
181     | {
182       "loadAddress": string decimal,
183       "id": decimal,
184       "tsc"?: string decimal,
185       "module"?: string,
186       "symbol"?: string,
187       "line"?: decimal,
188       "column"?: decimal,
189       "source"?: string,
190       "mnemonic"?: string,
191     }
192   */
193 public:
194   OutputWriterJSON(Stream &s, const TraceDumperOptions &options)
195       : m_s(s), m_options(options),
196         m_j(m_s.AsRawOstream(),
197             /*IndentSize=*/options.pretty_print_json ? 2 : 0) {
198     m_j.arrayBegin();
199   };
200 
201   ~OutputWriterJSON() { m_j.arrayEnd(); }
202 
203   void DumpEvent(const TraceDumper::TraceItem &item) {
204     m_j.attribute("event", TraceCursor::EventKindToString(*item.event));
205     if (item.event == eTraceEventCPUChanged)
206       m_j.attribute("cpuId", item.cpu_id);
207   }
208 
209   void DumpInstruction(const TraceDumper::TraceItem &item) {
210     m_j.attribute("loadAddress", formatv("{0:x}", item.load_address));
211     if (item.symbol_info) {
212       m_j.attribute("module", ToOptionalString(GetModuleName(item)));
213       m_j.attribute(
214           "symbol",
215           ToOptionalString(item.symbol_info->sc.GetFunctionName().AsCString()));
216 
217       if (item.symbol_info->instruction) {
218         m_j.attribute("mnemonic",
219                       ToOptionalString(item.symbol_info->instruction->GetMnemonic(
220                           &item.symbol_info->exe_ctx)));
221       }
222 
223       if (IsLineEntryValid(item.symbol_info->sc.line_entry)) {
224         m_j.attribute(
225             "source",
226             ToOptionalString(
227                 item.symbol_info->sc.line_entry.file.GetPath().c_str()));
228         m_j.attribute("line", item.symbol_info->sc.line_entry.line);
229         m_j.attribute("column", item.symbol_info->sc.line_entry.column);
230       }
231     }
232   }
233 
234   void TraceItem(const TraceDumper::TraceItem &item) override {
235     m_j.object([&] {
236       m_j.attribute("id", item.id);
237       if (m_options.show_tsc)
238         m_j.attribute(
239             "tsc",
240             item.tsc ? Optional<std::string>(std::to_string(*item.tsc)) : None);
241 
242       if (item.event) {
243         DumpEvent(item);
244       } else if (item.error) {
245         m_j.attribute("error", *item.error);
246       } else {
247         DumpInstruction(item);
248       }
249     });
250   }
251 
252 private:
253   Stream &m_s;
254   TraceDumperOptions m_options;
255   json::OStream m_j;
256 };
257 
258 static std::unique_ptr<TraceDumper::OutputWriter>
259 CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) {
260   if (options.json)
261     return std::unique_ptr<TraceDumper::OutputWriter>(
262         new OutputWriterJSON(s, options));
263   else
264     return std::unique_ptr<TraceDumper::OutputWriter>(
265         new OutputWriterCLI(s, options, thread));
266 }
267 
268 TraceDumper::TraceDumper(lldb::TraceCursorUP &&cursor_up, Stream &s,
269                          const TraceDumperOptions &options)
270     : m_cursor_up(std::move(cursor_up)), m_options(options),
271       m_writer_up(CreateWriter(
272           s, m_options, *m_cursor_up->GetExecutionContextRef().GetThreadSP())) {
273 
274   if (m_options.id)
275     m_cursor_up->GoToId(*m_options.id);
276   else if (m_options.forwards)
277     m_cursor_up->Seek(0, TraceCursor::SeekType::Beginning);
278   else
279     m_cursor_up->Seek(0, TraceCursor::SeekType::End);
280 
281   m_cursor_up->SetForwards(m_options.forwards);
282   if (m_options.skip) {
283     m_cursor_up->Seek((m_options.forwards ? 1 : -1) * *m_options.skip,
284                       TraceCursor::SeekType::Current);
285   }
286 }
287 
288 TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() {
289   TraceItem item;
290   item.id = m_cursor_up->GetId();
291 
292   if (m_options.show_tsc)
293     item.tsc = m_cursor_up->GetCounter(lldb::eTraceCounterTSC);
294   return item;
295 }
296 
297 /// Find the symbol context for the given address reusing the previous
298 /// instruction's symbol context when possible.
299 static SymbolContext
300 CalculateSymbolContext(const Address &address,
301                        const TraceDumper::SymbolInfo &prev_symbol_info) {
302   AddressRange range;
303   if (prev_symbol_info.sc.GetAddressRange(eSymbolContextEverything, 0,
304                                           /*inline_block_range*/ false,
305                                           range) &&
306       range.Contains(address))
307     return prev_symbol_info.sc;
308 
309   SymbolContext sc;
310   address.CalculateSymbolContext(&sc, eSymbolContextEverything);
311   return sc;
312 }
313 
314 /// Find the disassembler for the given address reusing the previous
315 /// instruction's disassembler when possible.
316 static std::tuple<DisassemblerSP, InstructionSP>
317 CalculateDisass(const TraceDumper::SymbolInfo &symbol_info,
318                 const TraceDumper::SymbolInfo &prev_symbol_info,
319                 const ExecutionContext &exe_ctx) {
320   if (prev_symbol_info.disassembler) {
321     if (InstructionSP instruction =
322             prev_symbol_info.disassembler->GetInstructionList()
323                 .GetInstructionAtAddress(symbol_info.address))
324       return std::make_tuple(prev_symbol_info.disassembler, instruction);
325   }
326 
327   if (symbol_info.sc.function) {
328     if (DisassemblerSP disassembler =
329             symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) {
330       if (InstructionSP instruction =
331               disassembler->GetInstructionList().GetInstructionAtAddress(
332                   symbol_info.address))
333         return std::make_tuple(disassembler, instruction);
334     }
335   }
336   // We fallback to a single instruction disassembler
337   Target &target = exe_ctx.GetTargetRef();
338   const ArchSpec arch = target.GetArchitecture();
339   AddressRange range(symbol_info.address, arch.GetMaximumOpcodeByteSize());
340   DisassemblerSP disassembler =
341       Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr,
342                                      /*flavor*/ nullptr, target, range);
343   return std::make_tuple(
344       disassembler,
345       disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress(
346                          symbol_info.address)
347                    : InstructionSP());
348 }
349 
350 Optional<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) {
351   ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP();
352 
353   SymbolInfo prev_symbol_info;
354   Optional<lldb::user_id_t> last_id;
355 
356   ExecutionContext exe_ctx;
357   thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx);
358 
359   for (size_t insn_seen = 0; insn_seen < count && m_cursor_up->HasValue();
360        m_cursor_up->Next()) {
361 
362     last_id = m_cursor_up->GetId();
363     TraceItem item = CreatRawTraceItem();
364 
365     if (m_cursor_up->IsEvent()) {
366       if (!m_options.show_events)
367         continue;
368       item.event = m_cursor_up->GetEventType();
369       if (*item.event == eTraceEventCPUChanged)
370         item.cpu_id = m_cursor_up->GetCPU();
371     } else if (m_cursor_up->IsError()) {
372       item.error = m_cursor_up->GetError();
373     } else {
374       insn_seen++;
375       item.load_address = m_cursor_up->GetLoadAddress();
376 
377       if (!m_options.raw) {
378         SymbolInfo symbol_info;
379         symbol_info.exe_ctx = exe_ctx;
380         symbol_info.address.SetLoadAddress(item.load_address,
381                                            exe_ctx.GetTargetPtr());
382         symbol_info.sc =
383             CalculateSymbolContext(symbol_info.address, prev_symbol_info);
384         std::tie(symbol_info.disassembler, symbol_info.instruction) =
385             CalculateDisass(symbol_info, prev_symbol_info, exe_ctx);
386         item.prev_symbol_info = prev_symbol_info;
387         item.symbol_info = symbol_info;
388         prev_symbol_info = symbol_info;
389       }
390     }
391     m_writer_up->TraceItem(item);
392   }
393   if (!m_cursor_up->HasValue())
394     m_writer_up->NoMoreData();
395   return last_id;
396 }
397