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