• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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