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