1 //===-- cli-wrapper-pt.cpp -------------------------------------*- C++ -*-===//
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 // CLI Wrapper of PTDecoder Tool to enable it to be used through LLDB's CLI. The
8 // wrapper provides a new command called processor-trace with 4 child
9 // subcommands as follows:
10 // processor-trace start
11 // processor-trace stop
12 // processor-trace show-trace-options
13 // processor-trace show-instr-log
14 //
15 //===----------------------------------------------------------------------===//
16 
17 #include <cerrno>
18 #include <cinttypes>
19 #include <cstring>
20 #include <string>
21 #include <vector>
22 
23 #include "PTDecoder.h"
24 #include "cli-wrapper-pt.h"
25 #include "lldb/API/SBCommandInterpreter.h"
26 #include "lldb/API/SBCommandReturnObject.h"
27 #include "lldb/API/SBDebugger.h"
28 #include "lldb/API/SBProcess.h"
29 #include "lldb/API/SBStream.h"
30 #include "lldb/API/SBStructuredData.h"
31 #include "lldb/API/SBTarget.h"
32 #include "lldb/API/SBThread.h"
33 
GetProcess(lldb::SBDebugger & debugger,lldb::SBCommandReturnObject & result,lldb::SBProcess & process)34 static bool GetProcess(lldb::SBDebugger &debugger,
35                        lldb::SBCommandReturnObject &result,
36                        lldb::SBProcess &process) {
37   if (!debugger.IsValid()) {
38     result.Printf("error: invalid debugger\n");
39     result.SetStatus(lldb::eReturnStatusFailed);
40     return false;
41   }
42 
43   lldb::SBTarget target = debugger.GetSelectedTarget();
44   if (!target.IsValid()) {
45     result.Printf("error: invalid target inside debugger\n");
46     result.SetStatus(lldb::eReturnStatusFailed);
47     return false;
48   }
49 
50   process = target.GetProcess();
51   if (!process.IsValid() ||
52       (process.GetState() == lldb::StateType::eStateDetached) ||
53       (process.GetState() == lldb::StateType::eStateExited) ||
54       (process.GetState() == lldb::StateType::eStateInvalid)) {
55     result.Printf("error: invalid process inside debugger's target\n");
56     result.SetStatus(lldb::eReturnStatusFailed);
57     return false;
58   }
59 
60   return true;
61 }
62 
ParseCommandOption(char ** command,lldb::SBCommandReturnObject & result,uint32_t & index,const std::string & arg,uint32_t & parsed_result)63 static bool ParseCommandOption(char **command,
64                                lldb::SBCommandReturnObject &result,
65                                uint32_t &index, const std::string &arg,
66                                uint32_t &parsed_result) {
67   char *endptr;
68   if (!command[++index]) {
69     result.Printf("error: option \"%s\" requires an argument\n", arg.c_str());
70     result.SetStatus(lldb::eReturnStatusFailed);
71     return false;
72   }
73 
74   errno = 0;
75   unsigned long output = strtoul(command[index], &endptr, 0);
76   if ((errno != 0) || (*endptr != '\0')) {
77     result.Printf("error: invalid value \"%s\" provided for option \"%s\"\n",
78                   command[index], arg.c_str());
79     result.SetStatus(lldb::eReturnStatusFailed);
80     return false;
81   }
82   if (output > UINT32_MAX) {
83     result.Printf("error: value \"%s\" for option \"%s\" exceeds UINT32_MAX\n",
84                   command[index], arg.c_str());
85     result.SetStatus(lldb::eReturnStatusFailed);
86     return false;
87   }
88   parsed_result = (uint32_t)output;
89   return true;
90 }
91 
ParseCommandArgThread(char ** command,lldb::SBCommandReturnObject & result,lldb::SBProcess & process,uint32_t & index,lldb::tid_t & thread_id)92 static bool ParseCommandArgThread(char **command,
93                                   lldb::SBCommandReturnObject &result,
94                                   lldb::SBProcess &process, uint32_t &index,
95                                   lldb::tid_t &thread_id) {
96   char *endptr;
97   if (!strcmp(command[index], "all"))
98     thread_id = LLDB_INVALID_THREAD_ID;
99   else {
100     uint32_t thread_index_id;
101     errno = 0;
102     unsigned long output = strtoul(command[index], &endptr, 0);
103     if ((errno != 0) || (*endptr != '\0') || (output > UINT32_MAX)) {
104       result.Printf("error: invalid thread specification: \"%s\"\n",
105                     command[index]);
106       result.SetStatus(lldb::eReturnStatusFailed);
107       return false;
108     }
109     thread_index_id = (uint32_t)output;
110 
111     lldb::SBThread thread = process.GetThreadByIndexID(thread_index_id);
112     if (!thread.IsValid()) {
113       result.Printf(
114           "error: process has no thread with thread specification: \"%s\"\n",
115           command[index]);
116       result.SetStatus(lldb::eReturnStatusFailed);
117       return false;
118     }
119     thread_id = thread.GetThreadID();
120   }
121   return true;
122 }
123 
124 class ProcessorTraceStart : public lldb::SBCommandPluginInterface {
125 public:
ProcessorTraceStart(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)126   ProcessorTraceStart(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
127       : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
128 
~ProcessorTraceStart()129   ~ProcessorTraceStart() {}
130 
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)131   virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
132                          lldb::SBCommandReturnObject &result) {
133     lldb::SBProcess process;
134     lldb::SBThread thread;
135     if (!GetProcess(debugger, result, process))
136       return false;
137 
138     // Default initialize API's arguments
139     lldb::SBTraceOptions lldb_SBTraceOptions;
140     uint32_t trace_buffer_size = m_default_trace_buff_size;
141     lldb::tid_t thread_id;
142 
143     // Parse Command line options
144     bool thread_argument_provided = false;
145     if (command) {
146       for (uint32_t i = 0; command[i]; i++) {
147         if (!strcmp(command[i], "-b")) {
148           if (!ParseCommandOption(command, result, i, "-b", trace_buffer_size))
149             return false;
150         } else {
151           thread_argument_provided = true;
152           if (!ParseCommandArgThread(command, result, process, i, thread_id))
153             return false;
154         }
155       }
156     }
157 
158     if (!thread_argument_provided) {
159       thread = process.GetSelectedThread();
160       if (!thread.IsValid()) {
161         result.Printf("error: invalid current selected thread\n");
162         result.SetStatus(lldb::eReturnStatusFailed);
163         return false;
164       }
165       thread_id = thread.GetThreadID();
166     }
167 
168     if (trace_buffer_size > m_max_trace_buff_size)
169       trace_buffer_size = m_max_trace_buff_size;
170 
171     // Set API's arguments with parsed values
172     lldb_SBTraceOptions.setType(lldb::TraceType::eTraceTypeProcessorTrace);
173     lldb_SBTraceOptions.setTraceBufferSize(trace_buffer_size);
174     lldb_SBTraceOptions.setMetaDataBufferSize(0);
175     lldb_SBTraceOptions.setThreadID(thread_id);
176     lldb::SBStream sb_stream;
177     sb_stream.Printf("{\"trace-tech\":\"intel-pt\"}");
178     lldb::SBStructuredData custom_params;
179     lldb::SBError error = custom_params.SetFromJSON(sb_stream);
180     if (!error.Success()) {
181       result.Printf("error: %s\n", error.GetCString());
182       result.SetStatus(lldb::eReturnStatusFailed);
183       return false;
184     }
185     lldb_SBTraceOptions.setTraceParams(custom_params);
186 
187     // Start trace
188     pt_decoder_sp->StartProcessorTrace(process, lldb_SBTraceOptions, error);
189     if (!error.Success()) {
190       result.Printf("error: %s\n", error.GetCString());
191       result.SetStatus(lldb::eReturnStatusFailed);
192       return false;
193     }
194     return true;
195   }
196 
197 private:
198   std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
199   const uint32_t m_max_trace_buff_size = 0x3fff;
200   const uint32_t m_default_trace_buff_size = 4096;
201 };
202 
203 class ProcessorTraceInfo : public lldb::SBCommandPluginInterface {
204 public:
ProcessorTraceInfo(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)205   ProcessorTraceInfo(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
206       : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
207 
~ProcessorTraceInfo()208   ~ProcessorTraceInfo() {}
209 
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)210   virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
211                          lldb::SBCommandReturnObject &result) {
212     lldb::SBProcess process;
213     lldb::SBThread thread;
214     if (!GetProcess(debugger, result, process))
215       return false;
216 
217     lldb::tid_t thread_id;
218 
219     // Parse command line options
220     bool thread_argument_provided = false;
221     if (command) {
222       for (uint32_t i = 0; command[i]; i++) {
223         thread_argument_provided = true;
224         if (!ParseCommandArgThread(command, result, process, i, thread_id))
225           return false;
226       }
227     }
228 
229     if (!thread_argument_provided) {
230       thread = process.GetSelectedThread();
231       if (!thread.IsValid()) {
232         result.Printf("error: invalid current selected thread\n");
233         result.SetStatus(lldb::eReturnStatusFailed);
234         return false;
235       }
236       thread_id = thread.GetThreadID();
237     }
238 
239     size_t loop_count = 1;
240     bool entire_process_tracing = false;
241     if (thread_id == LLDB_INVALID_THREAD_ID) {
242       entire_process_tracing = true;
243       loop_count = process.GetNumThreads();
244     }
245 
246     // Get trace information
247     lldb::SBError error;
248     lldb::SBCommandReturnObject res;
249     for (size_t i = 0; i < loop_count; i++) {
250       error.Clear();
251       res.Clear();
252 
253       if (entire_process_tracing)
254         thread = process.GetThreadAtIndex(i);
255       else
256         thread = process.GetThreadByID(thread_id);
257       thread_id = thread.GetThreadID();
258 
259       ptdecoder::PTTraceOptions options;
260       pt_decoder_sp->GetProcessorTraceInfo(process, thread_id, options, error);
261       if (!error.Success()) {
262         res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
263                    thread.GetIndexID(), thread_id, error.GetCString());
264         result.AppendMessage(res.GetOutput());
265         continue;
266       }
267 
268       lldb::SBStructuredData data = options.GetTraceParams(error);
269       if (!error.Success()) {
270         res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
271                    thread.GetIndexID(), thread_id, error.GetCString());
272         result.AppendMessage(res.GetOutput());
273         continue;
274       }
275 
276       lldb::SBStream s;
277       error = data.GetAsJSON(s);
278       if (!error.Success()) {
279         res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
280                    thread.GetIndexID(), thread_id, error.GetCString());
281         result.AppendMessage(res.GetOutput());
282         continue;
283       }
284 
285       res.Printf("thread #%" PRIu32 ": tid=%" PRIu64
286                  ", trace buffer size=%" PRIu64 ", meta buffer size=%" PRIu64
287                  ", trace type=%" PRIu32 ", custom trace params=%s",
288                  thread.GetIndexID(), thread_id, options.GetTraceBufferSize(),
289                  options.GetMetaDataBufferSize(), options.GetType(),
290                  s.GetData());
291       result.AppendMessage(res.GetOutput());
292     }
293     return true;
294   }
295 
296 private:
297   std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
298 };
299 
300 class ProcessorTraceShowInstrLog : public lldb::SBCommandPluginInterface {
301 public:
ProcessorTraceShowInstrLog(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)302   ProcessorTraceShowInstrLog(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
303       : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
304 
~ProcessorTraceShowInstrLog()305   ~ProcessorTraceShowInstrLog() {}
306 
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)307   virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
308                          lldb::SBCommandReturnObject &result) {
309     lldb::SBProcess process;
310     lldb::SBThread thread;
311     if (!GetProcess(debugger, result, process))
312       return false;
313 
314     // Default initialize API's arguments
315     uint32_t offset;
316     bool offset_provided = false;
317     uint32_t count = m_default_count;
318     lldb::tid_t thread_id;
319 
320     // Parse command line options
321     bool thread_argument_provided = false;
322     if (command) {
323       for (uint32_t i = 0; command[i]; i++) {
324         if (!strcmp(command[i], "-o")) {
325           if (!ParseCommandOption(command, result, i, "-o", offset))
326             return false;
327           offset_provided = true;
328         } else if (!strcmp(command[i], "-c")) {
329           if (!ParseCommandOption(command, result, i, "-c", count))
330             return false;
331         } else {
332           thread_argument_provided = true;
333           if (!ParseCommandArgThread(command, result, process, i, thread_id))
334             return false;
335         }
336       }
337     }
338 
339     if (!thread_argument_provided) {
340       thread = process.GetSelectedThread();
341       if (!thread.IsValid()) {
342         result.Printf("error: invalid current selected thread\n");
343         result.SetStatus(lldb::eReturnStatusFailed);
344         return false;
345       }
346       thread_id = thread.GetThreadID();
347     }
348 
349     size_t loop_count = 1;
350     bool entire_process_tracing = false;
351     if (thread_id == LLDB_INVALID_THREAD_ID) {
352       entire_process_tracing = true;
353       loop_count = process.GetNumThreads();
354     }
355 
356     // Get instruction log and disassemble it
357     lldb::SBError error;
358     lldb::SBCommandReturnObject res;
359     for (size_t i = 0; i < loop_count; i++) {
360       error.Clear();
361       res.Clear();
362 
363       if (entire_process_tracing)
364         thread = process.GetThreadAtIndex(i);
365       else
366         thread = process.GetThreadByID(thread_id);
367       thread_id = thread.GetThreadID();
368 
369       // If offset is not provided then calculate a default offset (to display
370       // last 'count' number of instructions)
371       if (!offset_provided)
372         offset = count - 1;
373 
374       // Get the instruction log
375       ptdecoder::PTInstructionList insn_list;
376       pt_decoder_sp->GetInstructionLogAtOffset(process, thread_id, offset,
377                                                count, insn_list, error);
378       if (!error.Success()) {
379         res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
380                    thread.GetIndexID(), thread_id, error.GetCString());
381         result.AppendMessage(res.GetOutput());
382         continue;
383       }
384 
385       // Disassemble the instruction log
386       std::string disassembler_command("dis -c 1 -s ");
387       res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 "\n", thread.GetIndexID(),
388                  thread_id);
389       lldb::SBCommandInterpreter sb_cmnd_interpreter(
390           debugger.GetCommandInterpreter());
391       lldb::SBCommandReturnObject result_obj;
392       for (size_t i = 0; i < insn_list.GetSize(); i++) {
393         ptdecoder::PTInstruction insn = insn_list.GetInstructionAtIndex(i);
394         uint64_t addr = insn.GetInsnAddress();
395         std::string error = insn.GetError();
396         if (!error.empty()) {
397           res.AppendMessage(error.c_str());
398           continue;
399         }
400 
401         result_obj.Clear();
402         std::string complete_disassembler_command =
403             disassembler_command + std::to_string(addr);
404         sb_cmnd_interpreter.HandleCommand(complete_disassembler_command.c_str(),
405                                           result_obj, false);
406         std::string result_str(result_obj.GetOutput());
407         if (result_str.empty()) {
408           lldb::SBCommandReturnObject output;
409           output.Printf(" Disassembly not found for address: %" PRIu64, addr);
410           res.AppendMessage(output.GetOutput());
411           continue;
412         }
413 
414         // LLDB's disassemble command displays assembly instructions along with
415         // the names of the functions they belong to. Parse this result to
416         // display only the assembly instructions and not the function names
417         // in an instruction log
418         std::size_t first_new_line_index = result_str.find_first_of('\n');
419         std::size_t last_new_line_index = result_str.find_last_of('\n');
420         if (first_new_line_index != last_new_line_index)
421           res.AppendMessage((result_str.substr(first_new_line_index + 1,
422                                                last_new_line_index -
423                                                    first_new_line_index - 1))
424                                 .c_str());
425         else
426           res.AppendMessage(
427               (result_str.substr(0, result_str.length() - 1)).c_str());
428       }
429       result.AppendMessage(res.GetOutput());
430     }
431     return true;
432   }
433 
434 private:
435   std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
436   const uint32_t m_default_count = 10;
437 };
438 
439 class ProcessorTraceStop : public lldb::SBCommandPluginInterface {
440 public:
ProcessorTraceStop(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)441   ProcessorTraceStop(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
442       : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
443 
~ProcessorTraceStop()444   ~ProcessorTraceStop() {}
445 
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)446   virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
447                          lldb::SBCommandReturnObject &result) {
448     lldb::SBProcess process;
449     lldb::SBThread thread;
450     if (!GetProcess(debugger, result, process))
451       return false;
452 
453     lldb::tid_t thread_id;
454 
455     // Parse command line options
456     bool thread_argument_provided = false;
457     if (command) {
458       for (uint32_t i = 0; command[i]; i++) {
459         thread_argument_provided = true;
460         if (!ParseCommandArgThread(command, result, process, i, thread_id))
461           return false;
462       }
463     }
464 
465     if (!thread_argument_provided) {
466       thread = process.GetSelectedThread();
467       if (!thread.IsValid()) {
468         result.Printf("error: invalid current selected thread\n");
469         result.SetStatus(lldb::eReturnStatusFailed);
470         return false;
471       }
472       thread_id = thread.GetThreadID();
473     }
474 
475     // Stop trace
476     lldb::SBError error;
477     pt_decoder_sp->StopProcessorTrace(process, error, thread_id);
478     if (!error.Success()) {
479       result.Printf("error: %s\n", error.GetCString());
480       result.SetStatus(lldb::eReturnStatusFailed);
481       return false;
482     }
483     return true;
484   }
485 
486 private:
487   std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
488 };
489 
PTPluginInitialize(lldb::SBDebugger & debugger)490 bool PTPluginInitialize(lldb::SBDebugger &debugger) {
491   lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
492   lldb::SBCommand proc_trace = interpreter.AddMultiwordCommand(
493       "processor-trace", "Intel(R) Processor Trace for thread/process");
494 
495   std::shared_ptr<ptdecoder::PTDecoder> PTDecoderSP(
496       new ptdecoder::PTDecoder(debugger));
497 
498   lldb::SBCommandPluginInterface *proc_trace_start =
499       new ProcessorTraceStart(PTDecoderSP);
500   const char *help_proc_trace_start = "start Intel(R) Processor Trace on a "
501                                       "specific thread or on the whole process";
502   const char *syntax_proc_trace_start =
503       "processor-trace start  <cmd-options>\n\n"
504       "\rcmd-options Usage:\n"
505       "\r  processor-trace start [-b <buffer-size>] [<thread-index>]\n\n"
506       "\t\b-b <buffer-size>\n"
507       "\t    size of the trace buffer to store the trace data. If not "
508       "specified then a default value will be taken\n\n"
509       "\t\b<thread-index>\n"
510       "\t    thread index of the thread. If no threads are specified, "
511       "currently selected thread is taken.\n"
512       "\t    Use the thread-index 'all' to start tracing the whole process\n";
513   proc_trace.AddCommand("start", proc_trace_start, help_proc_trace_start,
514                         syntax_proc_trace_start);
515 
516   lldb::SBCommandPluginInterface *proc_trace_stop =
517       new ProcessorTraceStop(PTDecoderSP);
518   const char *help_proc_trace_stop =
519       "stop Intel(R) Processor Trace on a specific thread or on whole process";
520   const char *syntax_proc_trace_stop =
521       "processor-trace stop  <cmd-options>\n\n"
522       "\rcmd-options Usage:\n"
523       "\r  processor-trace stop [<thread-index>]\n\n"
524       "\t\b<thread-index>\n"
525       "\t    thread index of the thread. If no threads are specified, "
526       "currently selected thread is taken.\n"
527       "\t    Use the thread-index 'all' to stop tracing the whole process\n";
528   proc_trace.AddCommand("stop", proc_trace_stop, help_proc_trace_stop,
529                         syntax_proc_trace_stop);
530 
531   lldb::SBCommandPluginInterface *proc_trace_show_instr_log =
532       new ProcessorTraceShowInstrLog(PTDecoderSP);
533   const char *help_proc_trace_show_instr_log =
534       "display a log of assembly instructions executed for a specific thread "
535       "or for the whole process.\n"
536       "The length of the log to be displayed and the offset in the whole "
537       "instruction log from where the log needs to be displayed can also be "
538       "provided. The offset is counted from the end of this whole "
539       "instruction log which means the last executed instruction is at offset "
540       "0 (zero)";
541   const char *syntax_proc_trace_show_instr_log =
542       "processor-trace show-instr-log  <cmd-options>\n\n"
543       "\rcmd-options Usage:\n"
544       "\r  processor-trace show-instr-log [-o <offset>] [-c <count>] "
545       "[<thread-index>]\n\n"
546       "\t\b-o <offset>\n"
547       "\t    offset in the whole instruction log from where the log will be "
548       "displayed. If not specified then a default value will be taken\n\n"
549       "\t\b-c <count>\n"
550       "\t    number of instructions to be displayed. If not specified then a "
551       "default value will be taken\n\n"
552       "\t\b<thread-index>\n"
553       "\t    thread index of the thread. If no threads are specified, "
554       "currently selected thread is taken.\n"
555       "\t    Use the thread-index 'all' to show instruction log for all the "
556       "threads of the process\n";
557   proc_trace.AddCommand("show-instr-log", proc_trace_show_instr_log,
558                         help_proc_trace_show_instr_log,
559                         syntax_proc_trace_show_instr_log);
560 
561   lldb::SBCommandPluginInterface *proc_trace_options =
562       new ProcessorTraceInfo(PTDecoderSP);
563   const char *help_proc_trace_show_options =
564       "display all the information regarding Intel(R) Processor Trace for a "
565       "specific thread or for the whole process.\n"
566       "The information contains trace buffer size and configuration options"
567       " of Intel(R) Processor Trace.";
568   const char *syntax_proc_trace_show_options =
569       "processor-trace show-options <cmd-options>\n\n"
570       "\rcmd-options Usage:\n"
571       "\r  processor-trace show-options [<thread-index>]\n\n"
572       "\t\b<thread-index>\n"
573       "\t    thread index of the thread. If no threads are specified, "
574       "currently selected thread is taken.\n"
575       "\t    Use the thread-index 'all' to display information for all threads "
576       "of the process\n";
577   proc_trace.AddCommand("show-trace-options", proc_trace_options,
578                         help_proc_trace_show_options,
579                         syntax_proc_trace_show_options);
580 
581   return true;
582 }
583