1 //===-- Trace.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/Trace.h"
10 
11 #include "llvm/Support/Format.h"
12 
13 #include "lldb/Core/Module.h"
14 #include "lldb/Core/PluginManager.h"
15 #include "lldb/Symbol/Function.h"
16 #include "lldb/Target/Process.h"
17 #include "lldb/Target/SectionLoadList.h"
18 #include "lldb/Target/Thread.h"
19 #include "lldb/Target/ThreadPostMortemTrace.h"
20 #include "lldb/Utility/Stream.h"
21 
22 using namespace lldb;
23 using namespace lldb_private;
24 using namespace llvm;
25 
26 // Helper structs used to extract the type of a trace session json without
27 // having to parse the entire object.
28 
29 struct JSONSimplePluginSettings {
30   std::string type;
31 };
32 
33 struct JSONSimpleTraceSession {
34   JSONSimplePluginSettings trace;
35 };
36 
37 namespace llvm {
38 namespace json {
39 
40 bool fromJSON(const Value &value, JSONSimplePluginSettings &plugin_settings,
41               Path path) {
42   json::ObjectMapper o(value, path);
43   return o && o.map("type", plugin_settings.type);
44 }
45 
46 bool fromJSON(const Value &value, JSONSimpleTraceSession &session, Path path) {
47   json::ObjectMapper o(value, path);
48   return o && o.map("trace", session.trace);
49 }
50 
51 } // namespace json
52 } // namespace llvm
53 
54 static Error createInvalidPlugInError(StringRef plugin_name) {
55   return createStringError(
56       std::errc::invalid_argument,
57       "no trace plug-in matches the specified type: \"%s\"",
58       plugin_name.data());
59 }
60 
61 Expected<lldb::TraceSP>
62 Trace::FindPluginForPostMortemProcess(Debugger &debugger,
63                                       const json::Value &trace_session_file,
64                                       StringRef session_file_dir) {
65   JSONSimpleTraceSession json_session;
66   json::Path::Root root("traceSession");
67   if (!json::fromJSON(trace_session_file, json_session, root))
68     return root.getError();
69 
70   ConstString plugin_name(json_session.trace.type);
71   if (auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name))
72     return create_callback(trace_session_file, session_file_dir, debugger);
73 
74   return createInvalidPlugInError(json_session.trace.type);
75 }
76 
77 Expected<lldb::TraceSP>
78 Trace::FindPluginForLiveProcess(llvm::StringRef plugin_name, Process &process) {
79   if (!process.IsLiveDebugSession())
80     return createStringError(inconvertibleErrorCode(),
81                              "Can't trace non-live processes");
82 
83   ConstString name(plugin_name);
84   if (auto create_callback =
85           PluginManager::GetTraceCreateCallbackForLiveProcess(name))
86     return create_callback(process);
87 
88   return createInvalidPlugInError(plugin_name);
89 }
90 
91 Expected<StringRef> Trace::FindPluginSchema(StringRef name) {
92   ConstString plugin_name(name);
93   StringRef schema = PluginManager::GetTraceSchema(plugin_name);
94   if (!schema.empty())
95     return schema;
96 
97   return createInvalidPlugInError(name);
98 }
99 
100 static int GetNumberOfDigits(size_t num) {
101   return num == 0 ? 1 : static_cast<int>(log10(num)) + 1;
102 }
103 
104 /// Dump the symbol context of the given instruction address if it's different
105 /// from the symbol context of the previous instruction in the trace.
106 ///
107 /// \param[in] prev_sc
108 ///     The symbol context of the previous instruction in the trace.
109 ///
110 /// \param[in] address
111 ///     The address whose symbol information will be dumped.
112 ///
113 /// \return
114 ///     The symbol context of the current address, which might differ from the
115 ///     previous one.
116 static SymbolContext DumpSymbolContext(Stream &s, const SymbolContext &prev_sc,
117                                        Target &target, const Address &address) {
118   AddressRange range;
119   if (prev_sc.GetAddressRange(eSymbolContextEverything, 0,
120                               /*inline_block_range*/ false, range) &&
121       range.ContainsFileAddress(address))
122     return prev_sc;
123 
124   SymbolContext sc;
125   address.CalculateSymbolContext(&sc, eSymbolContextEverything);
126 
127   if (!prev_sc.module_sp && !sc.module_sp)
128     return sc;
129   if (prev_sc.module_sp == sc.module_sp && !sc.function && !sc.symbol &&
130       !prev_sc.function && !prev_sc.symbol)
131     return sc;
132 
133   s.Printf("  ");
134 
135   if (!sc.module_sp)
136     s.Printf("(none)");
137   else if (!sc.function && !sc.symbol)
138     s.Printf("%s`(none)",
139              sc.module_sp->GetFileSpec().GetFilename().AsCString());
140   else
141     sc.DumpStopContext(&s, &target, address, /*show_fullpath*/ false,
142                        /*show_module*/ true, /*show_inlined_frames*/ false,
143                        /*show_function_arguments*/ true,
144                        /*show_function_name*/ true);
145   s.Printf("\n");
146   return sc;
147 }
148 
149 /// Dump an instruction given by its address using a given disassembler, unless
150 /// the instruction is not present in the disassembler.
151 ///
152 /// \param[in] disassembler
153 ///     A disassembler containing a certain instruction list.
154 ///
155 /// \param[in] address
156 ///     The address of the instruction to dump.
157 ///
158 /// \return
159 ///     \b true if the information could be dumped, \b false otherwise.
160 static bool TryDumpInstructionInfo(Stream &s,
161                                    const DisassemblerSP &disassembler,
162                                    const ExecutionContext &exe_ctx,
163                                    const Address &address) {
164   if (!disassembler)
165     return false;
166 
167   if (InstructionSP instruction =
168           disassembler->GetInstructionList().GetInstructionAtAddress(address)) {
169     instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false,
170                       /*max_opcode_byte_size*/ 0, &exe_ctx,
171                       /*sym_ctx*/ nullptr, /*prev_sym_ctx*/ nullptr,
172                       /*disassembly_addr_format*/ nullptr,
173                       /*max_address_text_size*/ 0);
174     return true;
175   }
176 
177   return false;
178 }
179 
180 /// Dump an instruction instruction given by its address.
181 ///
182 /// \param[in] prev_disassembler
183 ///     The disassembler that was used to dump the previous instruction in the
184 ///     trace. It is useful to avoid recomputations.
185 ///
186 /// \param[in] address
187 ///     The address of the instruction to dump.
188 ///
189 /// \return
190 ///     A disassembler that contains the given instruction, which might differ
191 ///     from the previous disassembler.
192 static DisassemblerSP
193 DumpInstructionInfo(Stream &s, const SymbolContext &sc,
194                     const DisassemblerSP &prev_disassembler,
195                     ExecutionContext &exe_ctx, const Address &address) {
196   // We first try to use the previous disassembler
197   if (TryDumpInstructionInfo(s, prev_disassembler, exe_ctx, address))
198     return prev_disassembler;
199 
200   // Now we try using the current function's disassembler
201   if (sc.function) {
202     DisassemblerSP disassembler =
203         sc.function->GetInstructions(exe_ctx, nullptr, true);
204     if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address))
205       return disassembler;
206   }
207 
208   // We fallback to disassembly one instruction
209   Target &target = exe_ctx.GetTargetRef();
210   const ArchSpec &arch = target.GetArchitecture();
211   AddressRange range(address, arch.GetMaximumOpcodeByteSize() * 1);
212   DisassemblerSP disassembler = Disassembler::DisassembleRange(
213       arch, /*plugin_name*/ nullptr,
214       /*flavor*/ nullptr, target, range, /*prefer_file_cache*/ true);
215   if (TryDumpInstructionInfo(s, disassembler, exe_ctx, address))
216     return disassembler;
217   return nullptr;
218 }
219 
220 void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count,
221                                   size_t end_position, bool raw) {
222   if (!IsTraced(thread)) {
223     s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(),
224              thread.GetID());
225     return;
226   }
227 
228   size_t instructions_count = GetInstructionCount(thread);
229   s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n",
230            thread.GetIndexID(), thread.GetID(), instructions_count);
231 
232   if (count == 0 || end_position >= instructions_count)
233     return;
234 
235   size_t start_position =
236       end_position + 1 < count ? 0 : end_position + 1 - count;
237 
238   int digits_count = GetNumberOfDigits(end_position);
239   auto printInstructionIndex = [&](size_t index) {
240     s.Printf("    [%*zu] ", digits_count, index);
241   };
242 
243   bool was_prev_instruction_an_error = false;
244   Target &target = thread.GetProcess()->GetTarget();
245 
246   SymbolContext sc;
247   DisassemblerSP disassembler;
248   ExecutionContext exe_ctx;
249   target.CalculateExecutionContext(exe_ctx);
250 
251   TraverseInstructions(
252       thread, start_position, TraceDirection::Forwards,
253       [&](size_t index, Expected<lldb::addr_t> load_address) -> bool {
254         if (load_address) {
255           // We print an empty line after a sequence of errors to show more
256           // clearly that there's a gap in the trace
257           if (was_prev_instruction_an_error)
258             s.Printf("    ...missing instructions\n");
259 
260           Address address;
261           if (!raw) {
262             target.GetSectionLoadList().ResolveLoadAddress(*load_address,
263                                                            address);
264 
265             sc = DumpSymbolContext(s, sc, target, address);
266           }
267 
268           printInstructionIndex(index);
269           s.Printf("0x%016" PRIx64 "    ", *load_address);
270 
271           if (!raw) {
272             disassembler =
273                 DumpInstructionInfo(s, sc, disassembler, exe_ctx, address);
274           }
275 
276           was_prev_instruction_an_error = false;
277         } else {
278           printInstructionIndex(index);
279           s << toString(load_address.takeError());
280           was_prev_instruction_an_error = true;
281           if (!raw)
282             sc = SymbolContext();
283         }
284 
285         s.Printf("\n");
286 
287         return index < end_position;
288       });
289 }
290 
291 Error Trace::Start(const llvm::json::Value &request) {
292   if (!m_live_process)
293     return createStringError(inconvertibleErrorCode(),
294                              "Tracing requires a live process.");
295   return m_live_process->TraceStart(request);
296 }
297 
298 Error Trace::StopProcess() {
299   if (!m_live_process)
300     return createStringError(inconvertibleErrorCode(),
301                              "Tracing requires a live process.");
302   return m_live_process->TraceStop(
303       TraceStopRequest(GetPluginName().AsCString()));
304 }
305 
306 Error Trace::StopThreads(const std::vector<lldb::tid_t> &tids) {
307   if (!m_live_process)
308     return createStringError(inconvertibleErrorCode(),
309                              "Tracing requires a live process.");
310   return m_live_process->TraceStop(
311       TraceStopRequest(GetPluginName().AsCString(), tids));
312 }
313 
314 Expected<std::string> Trace::GetLiveProcessState() {
315   if (!m_live_process)
316     return createStringError(inconvertibleErrorCode(),
317                              "Tracing requires a live process.");
318   return m_live_process->TraceGetState(GetPluginName().AsCString());
319 }
320 
321 Optional<size_t> Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid,
322                                                     llvm::StringRef kind) {
323   auto it = m_live_thread_data.find(tid);
324   if (it == m_live_thread_data.end())
325     return None;
326   std::unordered_map<std::string, size_t> &single_thread_data = it->second;
327   auto single_thread_data_it = single_thread_data.find(kind.str());
328   if (single_thread_data_it == single_thread_data.end())
329     return None;
330   return single_thread_data_it->second;
331 }
332 
333 Optional<size_t> Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) {
334   auto data_it = m_live_process_data.find(kind.str());
335   if (data_it == m_live_process_data.end())
336     return None;
337   return data_it->second;
338 }
339 
340 Expected<std::vector<uint8_t>>
341 Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) {
342   if (!m_live_process)
343     return createStringError(inconvertibleErrorCode(),
344                              "Tracing requires a live process.");
345   llvm::Optional<size_t> size = GetLiveThreadBinaryDataSize(tid, kind);
346   if (!size)
347     return createStringError(
348         inconvertibleErrorCode(),
349         "Tracing data \"%s\" is not available for thread %" PRIu64 ".",
350         kind.data(), tid);
351 
352   TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(),
353                                     static_cast<int64_t>(tid), 0,
354                                     static_cast<int64_t>(*size)};
355   return m_live_process->TraceGetBinaryData(request);
356 }
357 
358 Expected<std::vector<uint8_t>>
359 Trace::GetLiveProcessBinaryData(llvm::StringRef kind) {
360   if (!m_live_process)
361     return createStringError(inconvertibleErrorCode(),
362                              "Tracing requires a live process.");
363   llvm::Optional<size_t> size = GetLiveProcessBinaryDataSize(kind);
364   if (!size)
365     return createStringError(
366         inconvertibleErrorCode(),
367         "Tracing data \"%s\" is not available for the process.", kind.data());
368 
369   TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(),
370                                     None, 0, static_cast<int64_t>(*size)};
371   return m_live_process->TraceGetBinaryData(request);
372 }
373 
374 void Trace::RefreshLiveProcessState() {
375   if (!m_live_process)
376     return;
377 
378   uint32_t new_stop_id = m_live_process->GetStopID();
379   if (new_stop_id == m_stop_id)
380     return;
381 
382   m_stop_id = new_stop_id;
383   m_live_thread_data.clear();
384 
385   Expected<std::string> json_string = GetLiveProcessState();
386   if (!json_string) {
387     DoRefreshLiveProcessState(json_string.takeError());
388     return;
389   }
390   Expected<TraceGetStateResponse> live_process_state =
391       json::parse<TraceGetStateResponse>(*json_string, "TraceGetStateResponse");
392   if (!live_process_state) {
393     DoRefreshLiveProcessState(live_process_state.takeError());
394     return;
395   }
396 
397   for (const TraceThreadState &thread_state :
398        live_process_state->tracedThreads) {
399     for (const TraceBinaryData &item : thread_state.binaryData)
400       m_live_thread_data[thread_state.tid][item.kind] = item.size;
401   }
402 
403   for (const TraceBinaryData &item : live_process_state->processBinaryData)
404     m_live_process_data[item.kind] = item.size;
405 
406   DoRefreshLiveProcessState(std::move(live_process_state));
407 }
408