1 //===-- TraceInstructionDumper.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/TraceInstructionDumper.h"
10 
11 #include "lldb/Core/Module.h"
12 #include "lldb/Symbol/Function.h"
13 #include "lldb/Target/ExecutionContext.h"
14 #include "lldb/Target/Process.h"
15 #include "lldb/Target/SectionLoadList.h"
16 
17 using namespace lldb;
18 using namespace lldb_private;
19 using namespace llvm;
20 
TraceInstructionDumper(lldb::TraceCursorUP && cursor_up,int initial_index,bool raw,bool show_tsc)21 TraceInstructionDumper::TraceInstructionDumper(lldb::TraceCursorUP &&cursor_up,
22                                                int initial_index, bool raw,
23                                                bool show_tsc)
24     : m_cursor_up(std::move(cursor_up)), m_index(initial_index), m_raw(raw),
25       m_show_tsc(show_tsc) {}
26 
27 /// \return
28 ///     Return \b true if the cursor could move one step.
TryMoveOneStep()29 bool TraceInstructionDumper::TryMoveOneStep() {
30   if (!m_cursor_up->Next()) {
31     SetNoMoreData();
32     return false;
33   }
34   m_index += m_cursor_up->IsForwards() ? 1 : -1;
35   return true;
36 }
37 
38 /// \return
39 ///     The number of characters that would be needed to print the given
40 ///     integer.
GetNumberOfChars(int num)41 static int GetNumberOfChars(int num) {
42   if (num == 0)
43     return 1;
44   return (num < 0 ? 1 : 0) + static_cast<int>(log10(abs(num))) + 1;
45 }
46 
47 /// Helper struct that holds symbol, disassembly and address information of an
48 /// instruction.
49 struct InstructionSymbolInfo {
50   SymbolContext sc;
51   Address address;
52   lldb::addr_t load_address;
53   lldb::DisassemblerSP disassembler;
54   lldb::InstructionSP instruction;
55   lldb_private::ExecutionContext exe_ctx;
56 };
57 
58 // This custom LineEntry validator is neded because some line_entries have
59 // 0 as line, which is meaningless. Notice that LineEntry::IsValid only
60 // checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX.
IsLineEntryValid(const LineEntry & line_entry)61 static bool IsLineEntryValid(const LineEntry &line_entry) {
62   return line_entry.IsValid() && line_entry.line > 0;
63 }
64 
65 /// \return
66 ///     \b true if the provided line entries match line, column and source file.
67 ///     This function assumes that the line entries are valid.
FileLineAndColumnMatches(const LineEntry & a,const LineEntry & b)68 static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) {
69   if (a.line != b.line)
70     return false;
71   if (a.column != b.column)
72     return false;
73   return a.file == b.file;
74 }
75 
76 /// Compare the symbol contexts of the provided \a InstructionSymbolInfo
77 /// objects.
78 ///
79 /// \return
80 ///     \a true if both instructions belong to the same scope level analized
81 ///     in the following order:
82 ///       - module
83 ///       - symbol
84 ///       - function
85 ///       - line
86 static bool
IsSameInstructionSymbolContext(const InstructionSymbolInfo & prev_insn,const InstructionSymbolInfo & insn)87 IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn,
88                                const InstructionSymbolInfo &insn) {
89   // module checks
90   if (insn.sc.module_sp != prev_insn.sc.module_sp)
91     return false;
92 
93   // symbol checks
94   if (insn.sc.symbol != prev_insn.sc.symbol)
95     return false;
96 
97   // function checks
98   if (!insn.sc.function && !prev_insn.sc.function)
99     return true;
100   else if (insn.sc.function != prev_insn.sc.function)
101     return false;
102 
103   // line entry checks
104   const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry);
105   const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry);
106   if (curr_line_valid && prev_line_valid)
107     return FileLineAndColumnMatches(insn.sc.line_entry,
108                                     prev_insn.sc.line_entry);
109   return curr_line_valid == prev_line_valid;
110 }
111 
112 /// Dump the symbol context of the given instruction address if it's different
113 /// from the symbol context of the previous instruction in the trace.
114 ///
115 /// \param[in] prev_sc
116 ///     The symbol context of the previous instruction in the trace.
117 ///
118 /// \param[in] address
119 ///     The address whose symbol information will be dumped.
120 ///
121 /// \return
122 ///     The symbol context of the current address, which might differ from the
123 ///     previous one.
124 static void
DumpInstructionSymbolContext(Stream & s,Optional<InstructionSymbolInfo> prev_insn,InstructionSymbolInfo & insn)125 DumpInstructionSymbolContext(Stream &s,
126                              Optional<InstructionSymbolInfo> prev_insn,
127                              InstructionSymbolInfo &insn) {
128   if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn))
129     return;
130 
131   s.Printf("  ");
132 
133   if (!insn.sc.module_sp)
134     s.Printf("(none)");
135   else if (!insn.sc.function && !insn.sc.symbol)
136     s.Printf("%s`(none)",
137              insn.sc.module_sp->GetFileSpec().GetFilename().AsCString());
138   else
139     insn.sc.DumpStopContext(&s, insn.exe_ctx.GetTargetPtr(), insn.address,
140                             /*show_fullpath=*/false,
141                             /*show_module=*/true, /*show_inlined_frames=*/false,
142                             /*show_function_arguments=*/true,
143                             /*show_function_name=*/true);
144   s.Printf("\n");
145 }
146 
DumpInstructionDisassembly(Stream & s,InstructionSymbolInfo & insn)147 static void DumpInstructionDisassembly(Stream &s, InstructionSymbolInfo &insn) {
148   if (!insn.instruction)
149     return;
150   s.Printf("    ");
151   insn.instruction->Dump(&s, /*show_address=*/false, /*show_bytes=*/false,
152                          /*max_opcode_byte_size=*/0, &insn.exe_ctx, &insn.sc,
153                          /*prev_sym_ctx=*/nullptr,
154                          /*disassembly_addr_format=*/nullptr,
155                          /*max_address_text_size=*/0);
156 }
157 
SetNoMoreData()158 void TraceInstructionDumper::SetNoMoreData() { m_no_more_data = true; }
159 
HasMoreData()160 bool TraceInstructionDumper::HasMoreData() { return !m_no_more_data; }
161 
DumpInstructions(Stream & s,size_t count)162 void TraceInstructionDumper::DumpInstructions(Stream &s, size_t count) {
163   ThreadSP thread_sp = m_cursor_up->GetExecutionContextRef().GetThreadSP();
164   if (!thread_sp) {
165     s.Printf("invalid thread");
166     return;
167   }
168 
169   s.Printf("thread #%u: tid = %" PRIu64 "\n", thread_sp->GetIndexID(),
170            thread_sp->GetID());
171 
172   int digits_count = GetNumberOfChars(
173       m_cursor_up->IsForwards() ? m_index + count - 1 : m_index - count + 1);
174   bool was_prev_instruction_an_error = false;
175 
176   auto printMissingInstructionsMessage = [&]() {
177     s.Printf("    ...missing instructions\n");
178   };
179 
180   auto printInstructionIndex = [&]() {
181     s.Printf("    [%*d] ", digits_count, m_index);
182 
183     if (m_show_tsc) {
184       s.Printf("[tsc=");
185 
186       if (Optional<uint64_t> timestamp = m_cursor_up->GetTimestampCounter())
187         s.Printf("0x%016" PRIx64, *timestamp);
188       else
189         s.Printf("unavailable");
190 
191       s.Printf("] ");
192     }
193   };
194 
195   InstructionSymbolInfo prev_insn_info;
196 
197   Target &target = thread_sp->GetProcess()->GetTarget();
198   ExecutionContext exe_ctx;
199   target.CalculateExecutionContext(exe_ctx);
200   const ArchSpec &arch = target.GetArchitecture();
201 
202   // Find the symbol context for the given address reusing the previous
203   // instruction's symbol context when possible.
204   auto calculateSymbolContext = [&](const Address &address) {
205     AddressRange range;
206     if (prev_insn_info.sc.GetAddressRange(eSymbolContextEverything, 0,
207                                           /*inline_block_range*/ false,
208                                           range) &&
209         range.Contains(address))
210       return prev_insn_info.sc;
211 
212     SymbolContext sc;
213     address.CalculateSymbolContext(&sc, eSymbolContextEverything);
214     return sc;
215   };
216 
217   // Find the disassembler for the given address reusing the previous
218   // instruction's disassembler when possible.
219   auto calculateDisass = [&](const Address &address, const SymbolContext &sc) {
220     if (prev_insn_info.disassembler) {
221       if (InstructionSP instruction =
222               prev_insn_info.disassembler->GetInstructionList()
223                   .GetInstructionAtAddress(address))
224         return std::make_tuple(prev_insn_info.disassembler, instruction);
225     }
226 
227     if (sc.function) {
228       if (DisassemblerSP disassembler =
229               sc.function->GetInstructions(exe_ctx, nullptr)) {
230         if (InstructionSP instruction =
231                 disassembler->GetInstructionList().GetInstructionAtAddress(
232                     address))
233           return std::make_tuple(disassembler, instruction);
234       }
235     }
236     // We fallback to a single instruction disassembler
237     AddressRange range(address, arch.GetMaximumOpcodeByteSize());
238     DisassemblerSP disassembler =
239         Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr,
240                                        /*flavor*/ nullptr, target, range);
241     return std::make_tuple(disassembler,
242                            disassembler ? disassembler->GetInstructionList()
243                                               .GetInstructionAtAddress(address)
244                                         : InstructionSP());
245   };
246 
247   for (size_t i = 0; i < count; i++) {
248     if (!HasMoreData()) {
249       s.Printf("    no more data\n");
250       break;
251     }
252 
253     if (Error err = m_cursor_up->GetError()) {
254       if (!m_cursor_up->IsForwards() && !was_prev_instruction_an_error)
255         printMissingInstructionsMessage();
256 
257       was_prev_instruction_an_error = true;
258 
259       printInstructionIndex();
260       s << toString(std::move(err));
261     } else {
262       if (m_cursor_up->IsForwards() && was_prev_instruction_an_error)
263         printMissingInstructionsMessage();
264 
265       was_prev_instruction_an_error = false;
266 
267       InstructionSymbolInfo insn_info;
268 
269       if (!m_raw) {
270         insn_info.load_address = m_cursor_up->GetLoadAddress();
271         insn_info.exe_ctx = exe_ctx;
272         insn_info.address.SetLoadAddress(insn_info.load_address, &target);
273         insn_info.sc = calculateSymbolContext(insn_info.address);
274         std::tie(insn_info.disassembler, insn_info.instruction) =
275             calculateDisass(insn_info.address, insn_info.sc);
276 
277         DumpInstructionSymbolContext(s, prev_insn_info, insn_info);
278       }
279 
280       printInstructionIndex();
281       s.Printf("0x%016" PRIx64, m_cursor_up->GetLoadAddress());
282 
283       if (!m_raw)
284         DumpInstructionDisassembly(s, insn_info);
285 
286       prev_insn_info = insn_info;
287     }
288 
289     s.Printf("\n");
290     TryMoveOneStep();
291   }
292 }
293