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 result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
195 return true;
196 }
197
198 private:
199 std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
200 const uint32_t m_max_trace_buff_size = 0x3fff;
201 const uint32_t m_default_trace_buff_size = 4096;
202 };
203
204 class ProcessorTraceInfo : public lldb::SBCommandPluginInterface {
205 public:
ProcessorTraceInfo(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)206 ProcessorTraceInfo(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
207 : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
208
~ProcessorTraceInfo()209 ~ProcessorTraceInfo() {}
210
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)211 virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
212 lldb::SBCommandReturnObject &result) {
213 lldb::SBProcess process;
214 lldb::SBThread thread;
215 if (!GetProcess(debugger, result, process))
216 return false;
217
218 lldb::tid_t thread_id;
219
220 // Parse command line options
221 bool thread_argument_provided = false;
222 if (command) {
223 for (uint32_t i = 0; command[i]; i++) {
224 thread_argument_provided = true;
225 if (!ParseCommandArgThread(command, result, process, i, thread_id))
226 return false;
227 }
228 }
229
230 if (!thread_argument_provided) {
231 thread = process.GetSelectedThread();
232 if (!thread.IsValid()) {
233 result.Printf("error: invalid current selected thread\n");
234 result.SetStatus(lldb::eReturnStatusFailed);
235 return false;
236 }
237 thread_id = thread.GetThreadID();
238 }
239
240 size_t loop_count = 1;
241 bool entire_process_tracing = false;
242 if (thread_id == LLDB_INVALID_THREAD_ID) {
243 entire_process_tracing = true;
244 loop_count = process.GetNumThreads();
245 }
246
247 // Get trace information
248 lldb::SBError error;
249 lldb::SBCommandReturnObject res;
250 for (size_t i = 0; i < loop_count; i++) {
251 error.Clear();
252 res.Clear();
253
254 if (entire_process_tracing)
255 thread = process.GetThreadAtIndex(i);
256 else
257 thread = process.GetThreadByID(thread_id);
258 thread_id = thread.GetThreadID();
259
260 ptdecoder::PTTraceOptions options;
261 pt_decoder_sp->GetProcessorTraceInfo(process, thread_id, options, error);
262 if (!error.Success()) {
263 res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
264 thread.GetIndexID(), thread_id, error.GetCString());
265 result.AppendMessage(res.GetOutput());
266 continue;
267 }
268
269 lldb::SBStructuredData data = options.GetTraceParams(error);
270 if (!error.Success()) {
271 res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
272 thread.GetIndexID(), thread_id, error.GetCString());
273 result.AppendMessage(res.GetOutput());
274 continue;
275 }
276
277 lldb::SBStream s;
278 error = data.GetAsJSON(s);
279 if (!error.Success()) {
280 res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
281 thread.GetIndexID(), thread_id, error.GetCString());
282 result.AppendMessage(res.GetOutput());
283 continue;
284 }
285
286 res.Printf("thread #%" PRIu32 ": tid=%" PRIu64
287 ", trace buffer size=%" PRIu64 ", meta buffer size=%" PRIu64
288 ", trace type=%" PRIu32 ", custom trace params=%s",
289 thread.GetIndexID(), thread_id, options.GetTraceBufferSize(),
290 options.GetMetaDataBufferSize(), options.GetType(),
291 s.GetData());
292 result.AppendMessage(res.GetOutput());
293 }
294 result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
295 return true;
296 }
297
298 private:
299 std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
300 };
301
302 class ProcessorTraceShowInstrLog : public lldb::SBCommandPluginInterface {
303 public:
ProcessorTraceShowInstrLog(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)304 ProcessorTraceShowInstrLog(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
305 : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
306
~ProcessorTraceShowInstrLog()307 ~ProcessorTraceShowInstrLog() {}
308
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)309 virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
310 lldb::SBCommandReturnObject &result) {
311 lldb::SBProcess process;
312 lldb::SBThread thread;
313 if (!GetProcess(debugger, result, process))
314 return false;
315
316 // Default initialize API's arguments
317 uint32_t offset;
318 bool offset_provided = false;
319 uint32_t count = m_default_count;
320 lldb::tid_t thread_id;
321
322 // Parse command line options
323 bool thread_argument_provided = false;
324 if (command) {
325 for (uint32_t i = 0; command[i]; i++) {
326 if (!strcmp(command[i], "-o")) {
327 if (!ParseCommandOption(command, result, i, "-o", offset))
328 return false;
329 offset_provided = true;
330 } else if (!strcmp(command[i], "-c")) {
331 if (!ParseCommandOption(command, result, i, "-c", count))
332 return false;
333 } else {
334 thread_argument_provided = true;
335 if (!ParseCommandArgThread(command, result, process, i, thread_id))
336 return false;
337 }
338 }
339 }
340
341 if (!thread_argument_provided) {
342 thread = process.GetSelectedThread();
343 if (!thread.IsValid()) {
344 result.Printf("error: invalid current selected thread\n");
345 result.SetStatus(lldb::eReturnStatusFailed);
346 return false;
347 }
348 thread_id = thread.GetThreadID();
349 }
350
351 size_t loop_count = 1;
352 bool entire_process_tracing = false;
353 if (thread_id == LLDB_INVALID_THREAD_ID) {
354 entire_process_tracing = true;
355 loop_count = process.GetNumThreads();
356 }
357
358 // Get instruction log and disassemble it
359 lldb::SBError error;
360 lldb::SBCommandReturnObject res;
361 for (size_t i = 0; i < loop_count; i++) {
362 error.Clear();
363 res.Clear();
364
365 if (entire_process_tracing)
366 thread = process.GetThreadAtIndex(i);
367 else
368 thread = process.GetThreadByID(thread_id);
369 thread_id = thread.GetThreadID();
370
371 // If offset is not provided then calculate a default offset (to display
372 // last 'count' number of instructions)
373 if (!offset_provided)
374 offset = count - 1;
375
376 // Get the instruction log
377 ptdecoder::PTInstructionList insn_list;
378 pt_decoder_sp->GetInstructionLogAtOffset(process, thread_id, offset,
379 count, insn_list, error);
380 if (!error.Success()) {
381 res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s",
382 thread.GetIndexID(), thread_id, error.GetCString());
383 result.AppendMessage(res.GetOutput());
384 continue;
385 }
386
387 // Disassemble the instruction log
388 std::string disassembler_command("dis -c 1 -s ");
389 res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 "\n", thread.GetIndexID(),
390 thread_id);
391 lldb::SBCommandInterpreter sb_cmnd_interpreter(
392 debugger.GetCommandInterpreter());
393 lldb::SBCommandReturnObject result_obj;
394 for (size_t i = 0; i < insn_list.GetSize(); i++) {
395 ptdecoder::PTInstruction insn = insn_list.GetInstructionAtIndex(i);
396 uint64_t addr = insn.GetInsnAddress();
397 std::string error = insn.GetError();
398 if (!error.empty()) {
399 res.AppendMessage(error.c_str());
400 continue;
401 }
402
403 result_obj.Clear();
404 std::string complete_disassembler_command =
405 disassembler_command + std::to_string(addr);
406 sb_cmnd_interpreter.HandleCommand(complete_disassembler_command.c_str(),
407 result_obj, false);
408 std::string result_str(result_obj.GetOutput());
409 if (result_str.empty()) {
410 lldb::SBCommandReturnObject output;
411 output.Printf(" Disassembly not found for address: %" PRIu64, addr);
412 res.AppendMessage(output.GetOutput());
413 continue;
414 }
415
416 // LLDB's disassemble command displays assembly instructions along with
417 // the names of the functions they belong to. Parse this result to
418 // display only the assembly instructions and not the function names
419 // in an instruction log
420 std::size_t first_new_line_index = result_str.find_first_of('\n');
421 std::size_t last_new_line_index = result_str.find_last_of('\n');
422 if (first_new_line_index != last_new_line_index)
423 res.AppendMessage((result_str.substr(first_new_line_index + 1,
424 last_new_line_index -
425 first_new_line_index - 1))
426 .c_str());
427 else
428 res.AppendMessage(
429 (result_str.substr(0, result_str.length() - 1)).c_str());
430 }
431 result.AppendMessage(res.GetOutput());
432 }
433 result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
434 return true;
435 }
436
437 private:
438 std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
439 const uint32_t m_default_count = 10;
440 };
441
442 class ProcessorTraceStop : public lldb::SBCommandPluginInterface {
443 public:
ProcessorTraceStop(std::shared_ptr<ptdecoder::PTDecoder> & pt_decoder)444 ProcessorTraceStop(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder)
445 : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {}
446
~ProcessorTraceStop()447 ~ProcessorTraceStop() {}
448
DoExecute(lldb::SBDebugger debugger,char ** command,lldb::SBCommandReturnObject & result)449 virtual bool DoExecute(lldb::SBDebugger debugger, char **command,
450 lldb::SBCommandReturnObject &result) {
451 lldb::SBProcess process;
452 lldb::SBThread thread;
453 if (!GetProcess(debugger, result, process))
454 return false;
455
456 lldb::tid_t thread_id;
457
458 // Parse command line options
459 bool thread_argument_provided = false;
460 if (command) {
461 for (uint32_t i = 0; command[i]; i++) {
462 thread_argument_provided = true;
463 if (!ParseCommandArgThread(command, result, process, i, thread_id))
464 return false;
465 }
466 }
467
468 if (!thread_argument_provided) {
469 thread = process.GetSelectedThread();
470 if (!thread.IsValid()) {
471 result.Printf("error: invalid current selected thread\n");
472 result.SetStatus(lldb::eReturnStatusFailed);
473 return false;
474 }
475 thread_id = thread.GetThreadID();
476 }
477
478 // Stop trace
479 lldb::SBError error;
480 pt_decoder_sp->StopProcessorTrace(process, error, thread_id);
481 if (!error.Success()) {
482 result.Printf("error: %s\n", error.GetCString());
483 result.SetStatus(lldb::eReturnStatusFailed);
484 return false;
485 }
486 result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
487 return true;
488 }
489
490 private:
491 std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp;
492 };
493
PTPluginInitialize(lldb::SBDebugger & debugger)494 bool PTPluginInitialize(lldb::SBDebugger &debugger) {
495 lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
496 lldb::SBCommand proc_trace = interpreter.AddMultiwordCommand(
497 "processor-trace", "Intel(R) Processor Trace for thread/process");
498
499 std::shared_ptr<ptdecoder::PTDecoder> PTDecoderSP(
500 new ptdecoder::PTDecoder(debugger));
501
502 lldb::SBCommandPluginInterface *proc_trace_start =
503 new ProcessorTraceStart(PTDecoderSP);
504 const char *help_proc_trace_start = "start Intel(R) Processor Trace on a "
505 "specific thread or on the whole process";
506 const char *syntax_proc_trace_start =
507 "processor-trace start <cmd-options>\n\n"
508 "\rcmd-options Usage:\n"
509 "\r processor-trace start [-b <buffer-size>] [<thread-index>]\n\n"
510 "\t\b-b <buffer-size>\n"
511 "\t size of the trace buffer to store the trace data. If not "
512 "specified then a default value will be taken\n\n"
513 "\t\b<thread-index>\n"
514 "\t thread index of the thread. If no threads are specified, "
515 "currently selected thread is taken.\n"
516 "\t Use the thread-index 'all' to start tracing the whole process\n";
517 proc_trace.AddCommand("start", proc_trace_start, help_proc_trace_start,
518 syntax_proc_trace_start);
519
520 lldb::SBCommandPluginInterface *proc_trace_stop =
521 new ProcessorTraceStop(PTDecoderSP);
522 const char *help_proc_trace_stop =
523 "stop Intel(R) Processor Trace on a specific thread or on whole process";
524 const char *syntax_proc_trace_stop =
525 "processor-trace stop <cmd-options>\n\n"
526 "\rcmd-options Usage:\n"
527 "\r processor-trace stop [<thread-index>]\n\n"
528 "\t\b<thread-index>\n"
529 "\t thread index of the thread. If no threads are specified, "
530 "currently selected thread is taken.\n"
531 "\t Use the thread-index 'all' to stop tracing the whole process\n";
532 proc_trace.AddCommand("stop", proc_trace_stop, help_proc_trace_stop,
533 syntax_proc_trace_stop);
534
535 lldb::SBCommandPluginInterface *proc_trace_show_instr_log =
536 new ProcessorTraceShowInstrLog(PTDecoderSP);
537 const char *help_proc_trace_show_instr_log =
538 "display a log of assembly instructions executed for a specific thread "
539 "or for the whole process.\n"
540 "The length of the log to be displayed and the offset in the whole "
541 "instruction log from where the log needs to be displayed can also be "
542 "provided. The offset is counted from the end of this whole "
543 "instruction log which means the last executed instruction is at offset "
544 "0 (zero)";
545 const char *syntax_proc_trace_show_instr_log =
546 "processor-trace show-instr-log <cmd-options>\n\n"
547 "\rcmd-options Usage:\n"
548 "\r processor-trace show-instr-log [-o <offset>] [-c <count>] "
549 "[<thread-index>]\n\n"
550 "\t\b-o <offset>\n"
551 "\t offset in the whole instruction log from where the log will be "
552 "displayed. If not specified then a default value will be taken\n\n"
553 "\t\b-c <count>\n"
554 "\t number of instructions to be displayed. If not specified then a "
555 "default value will be taken\n\n"
556 "\t\b<thread-index>\n"
557 "\t thread index of the thread. If no threads are specified, "
558 "currently selected thread is taken.\n"
559 "\t Use the thread-index 'all' to show instruction log for all the "
560 "threads of the process\n";
561 proc_trace.AddCommand("show-instr-log", proc_trace_show_instr_log,
562 help_proc_trace_show_instr_log,
563 syntax_proc_trace_show_instr_log);
564
565 lldb::SBCommandPluginInterface *proc_trace_options =
566 new ProcessorTraceInfo(PTDecoderSP);
567 const char *help_proc_trace_show_options =
568 "display all the information regarding Intel(R) Processor Trace for a "
569 "specific thread or for the whole process.\n"
570 "The information contains trace buffer size and configuration options"
571 " of Intel(R) Processor Trace.";
572 const char *syntax_proc_trace_show_options =
573 "processor-trace show-options <cmd-options>\n\n"
574 "\rcmd-options Usage:\n"
575 "\r processor-trace show-options [<thread-index>]\n\n"
576 "\t\b<thread-index>\n"
577 "\t thread index of the thread. If no threads are specified, "
578 "currently selected thread is taken.\n"
579 "\t Use the thread-index 'all' to display information for all threads "
580 "of the process\n";
581 proc_trace.AddCommand("show-trace-options", proc_trace_options,
582 help_proc_trace_show_options,
583 syntax_proc_trace_show_options);
584
585 return true;
586 }
587