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