| //===-- cli-wrapper-pt.cpp -------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| // CLI Wrapper of PTDecoder Tool to enable it to be used through LLDB's CLI. The |
| // wrapper provides a new command called processor-trace with 4 child |
| // subcommands as follows: |
| // processor-trace start |
| // processor-trace stop |
| // processor-trace show-trace-options |
| // processor-trace show-instr-log |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <cerrno> |
| #include <cinttypes> |
| #include <cstring> |
| #include <string> |
| #include <vector> |
| |
| #include "PTDecoder.h" |
| #include "cli-wrapper-pt.h" |
| #include "lldb/API/SBCommandInterpreter.h" |
| #include "lldb/API/SBCommandReturnObject.h" |
| #include "lldb/API/SBDebugger.h" |
| #include "lldb/API/SBProcess.h" |
| #include "lldb/API/SBStream.h" |
| #include "lldb/API/SBStructuredData.h" |
| #include "lldb/API/SBTarget.h" |
| #include "lldb/API/SBThread.h" |
| |
| static bool GetProcess(lldb::SBDebugger &debugger, |
| lldb::SBCommandReturnObject &result, |
| lldb::SBProcess &process) { |
| if (!debugger.IsValid()) { |
| result.Printf("error: invalid debugger\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| |
| lldb::SBTarget target = debugger.GetSelectedTarget(); |
| if (!target.IsValid()) { |
| result.Printf("error: invalid target inside debugger\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| |
| process = target.GetProcess(); |
| if (!process.IsValid() || |
| (process.GetState() == lldb::StateType::eStateDetached) || |
| (process.GetState() == lldb::StateType::eStateExited) || |
| (process.GetState() == lldb::StateType::eStateInvalid)) { |
| result.Printf("error: invalid process inside debugger's target\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool ParseCommandOption(char **command, |
| lldb::SBCommandReturnObject &result, |
| uint32_t &index, const std::string &arg, |
| uint32_t &parsed_result) { |
| char *endptr; |
| if (!command[++index]) { |
| result.Printf("error: option \"%s\" requires an argument\n", arg.c_str()); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| |
| errno = 0; |
| unsigned long output = strtoul(command[index], &endptr, 0); |
| if ((errno != 0) || (*endptr != '\0')) { |
| result.Printf("error: invalid value \"%s\" provided for option \"%s\"\n", |
| command[index], arg.c_str()); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| if (output > UINT32_MAX) { |
| result.Printf("error: value \"%s\" for option \"%s\" exceeds UINT32_MAX\n", |
| command[index], arg.c_str()); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| parsed_result = (uint32_t)output; |
| return true; |
| } |
| |
| static bool ParseCommandArgThread(char **command, |
| lldb::SBCommandReturnObject &result, |
| lldb::SBProcess &process, uint32_t &index, |
| lldb::tid_t &thread_id) { |
| char *endptr; |
| if (!strcmp(command[index], "all")) |
| thread_id = LLDB_INVALID_THREAD_ID; |
| else { |
| uint32_t thread_index_id; |
| errno = 0; |
| unsigned long output = strtoul(command[index], &endptr, 0); |
| if ((errno != 0) || (*endptr != '\0') || (output > UINT32_MAX)) { |
| result.Printf("error: invalid thread specification: \"%s\"\n", |
| command[index]); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| thread_index_id = (uint32_t)output; |
| |
| lldb::SBThread thread = process.GetThreadByIndexID(thread_index_id); |
| if (!thread.IsValid()) { |
| result.Printf( |
| "error: process has no thread with thread specification: \"%s\"\n", |
| command[index]); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| thread_id = thread.GetThreadID(); |
| } |
| return true; |
| } |
| |
| class ProcessorTraceStart : public lldb::SBCommandPluginInterface { |
| public: |
| ProcessorTraceStart(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder) |
| : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {} |
| |
| ~ProcessorTraceStart() {} |
| |
| virtual bool DoExecute(lldb::SBDebugger debugger, char **command, |
| lldb::SBCommandReturnObject &result) { |
| lldb::SBProcess process; |
| lldb::SBThread thread; |
| if (!GetProcess(debugger, result, process)) |
| return false; |
| |
| // Default initialize API's arguments |
| lldb::SBTraceOptions lldb_SBTraceOptions; |
| uint32_t trace_buffer_size = m_default_trace_buff_size; |
| lldb::tid_t thread_id; |
| |
| // Parse Command line options |
| bool thread_argument_provided = false; |
| if (command) { |
| for (uint32_t i = 0; command[i]; i++) { |
| if (!strcmp(command[i], "-b")) { |
| if (!ParseCommandOption(command, result, i, "-b", trace_buffer_size)) |
| return false; |
| } else { |
| thread_argument_provided = true; |
| if (!ParseCommandArgThread(command, result, process, i, thread_id)) |
| return false; |
| } |
| } |
| } |
| |
| if (!thread_argument_provided) { |
| thread = process.GetSelectedThread(); |
| if (!thread.IsValid()) { |
| result.Printf("error: invalid current selected thread\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| thread_id = thread.GetThreadID(); |
| } |
| |
| if (trace_buffer_size > m_max_trace_buff_size) |
| trace_buffer_size = m_max_trace_buff_size; |
| |
| // Set API's arguments with parsed values |
| lldb_SBTraceOptions.setType(lldb::TraceType::eTraceTypeProcessorTrace); |
| lldb_SBTraceOptions.setTraceBufferSize(trace_buffer_size); |
| lldb_SBTraceOptions.setMetaDataBufferSize(0); |
| lldb_SBTraceOptions.setThreadID(thread_id); |
| lldb::SBStream sb_stream; |
| sb_stream.Printf("{\"trace-tech\":\"intel-pt\"}"); |
| lldb::SBStructuredData custom_params; |
| lldb::SBError error = custom_params.SetFromJSON(sb_stream); |
| if (!error.Success()) { |
| result.Printf("error: %s\n", error.GetCString()); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| lldb_SBTraceOptions.setTraceParams(custom_params); |
| |
| // Start trace |
| pt_decoder_sp->StartProcessorTrace(process, lldb_SBTraceOptions, error); |
| if (!error.Success()) { |
| result.Printf("error: %s\n", error.GetCString()); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp; |
| const uint32_t m_max_trace_buff_size = 0x3fff; |
| const uint32_t m_default_trace_buff_size = 4096; |
| }; |
| |
| class ProcessorTraceInfo : public lldb::SBCommandPluginInterface { |
| public: |
| ProcessorTraceInfo(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder) |
| : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {} |
| |
| ~ProcessorTraceInfo() {} |
| |
| virtual bool DoExecute(lldb::SBDebugger debugger, char **command, |
| lldb::SBCommandReturnObject &result) { |
| lldb::SBProcess process; |
| lldb::SBThread thread; |
| if (!GetProcess(debugger, result, process)) |
| return false; |
| |
| lldb::tid_t thread_id; |
| |
| // Parse command line options |
| bool thread_argument_provided = false; |
| if (command) { |
| for (uint32_t i = 0; command[i]; i++) { |
| thread_argument_provided = true; |
| if (!ParseCommandArgThread(command, result, process, i, thread_id)) |
| return false; |
| } |
| } |
| |
| if (!thread_argument_provided) { |
| thread = process.GetSelectedThread(); |
| if (!thread.IsValid()) { |
| result.Printf("error: invalid current selected thread\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| thread_id = thread.GetThreadID(); |
| } |
| |
| size_t loop_count = 1; |
| bool entire_process_tracing = false; |
| if (thread_id == LLDB_INVALID_THREAD_ID) { |
| entire_process_tracing = true; |
| loop_count = process.GetNumThreads(); |
| } |
| |
| // Get trace information |
| lldb::SBError error; |
| lldb::SBCommandReturnObject res; |
| for (size_t i = 0; i < loop_count; i++) { |
| error.Clear(); |
| res.Clear(); |
| |
| if (entire_process_tracing) |
| thread = process.GetThreadAtIndex(i); |
| else |
| thread = process.GetThreadByID(thread_id); |
| thread_id = thread.GetThreadID(); |
| |
| ptdecoder::PTTraceOptions options; |
| pt_decoder_sp->GetProcessorTraceInfo(process, thread_id, options, error); |
| if (!error.Success()) { |
| res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s", |
| thread.GetIndexID(), thread_id, error.GetCString()); |
| result.AppendMessage(res.GetOutput()); |
| continue; |
| } |
| |
| lldb::SBStructuredData data = options.GetTraceParams(error); |
| if (!error.Success()) { |
| res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s", |
| thread.GetIndexID(), thread_id, error.GetCString()); |
| result.AppendMessage(res.GetOutput()); |
| continue; |
| } |
| |
| lldb::SBStream s; |
| error = data.GetAsJSON(s); |
| if (!error.Success()) { |
| res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s", |
| thread.GetIndexID(), thread_id, error.GetCString()); |
| result.AppendMessage(res.GetOutput()); |
| continue; |
| } |
| |
| res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 |
| ", trace buffer size=%" PRIu64 ", meta buffer size=%" PRIu64 |
| ", trace type=%" PRIu32 ", custom trace params=%s", |
| thread.GetIndexID(), thread_id, options.GetTraceBufferSize(), |
| options.GetMetaDataBufferSize(), options.GetType(), |
| s.GetData()); |
| result.AppendMessage(res.GetOutput()); |
| } |
| return true; |
| } |
| |
| private: |
| std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp; |
| }; |
| |
| class ProcessorTraceShowInstrLog : public lldb::SBCommandPluginInterface { |
| public: |
| ProcessorTraceShowInstrLog(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder) |
| : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {} |
| |
| ~ProcessorTraceShowInstrLog() {} |
| |
| virtual bool DoExecute(lldb::SBDebugger debugger, char **command, |
| lldb::SBCommandReturnObject &result) { |
| lldb::SBProcess process; |
| lldb::SBThread thread; |
| if (!GetProcess(debugger, result, process)) |
| return false; |
| |
| // Default initialize API's arguments |
| uint32_t offset; |
| bool offset_provided = false; |
| uint32_t count = m_default_count; |
| lldb::tid_t thread_id; |
| |
| // Parse command line options |
| bool thread_argument_provided = false; |
| if (command) { |
| for (uint32_t i = 0; command[i]; i++) { |
| if (!strcmp(command[i], "-o")) { |
| if (!ParseCommandOption(command, result, i, "-o", offset)) |
| return false; |
| offset_provided = true; |
| } else if (!strcmp(command[i], "-c")) { |
| if (!ParseCommandOption(command, result, i, "-c", count)) |
| return false; |
| } else { |
| thread_argument_provided = true; |
| if (!ParseCommandArgThread(command, result, process, i, thread_id)) |
| return false; |
| } |
| } |
| } |
| |
| if (!thread_argument_provided) { |
| thread = process.GetSelectedThread(); |
| if (!thread.IsValid()) { |
| result.Printf("error: invalid current selected thread\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| thread_id = thread.GetThreadID(); |
| } |
| |
| size_t loop_count = 1; |
| bool entire_process_tracing = false; |
| if (thread_id == LLDB_INVALID_THREAD_ID) { |
| entire_process_tracing = true; |
| loop_count = process.GetNumThreads(); |
| } |
| |
| // Get instruction log and disassemble it |
| lldb::SBError error; |
| lldb::SBCommandReturnObject res; |
| for (size_t i = 0; i < loop_count; i++) { |
| error.Clear(); |
| res.Clear(); |
| |
| if (entire_process_tracing) |
| thread = process.GetThreadAtIndex(i); |
| else |
| thread = process.GetThreadByID(thread_id); |
| thread_id = thread.GetThreadID(); |
| |
| // If offset is not provided then calculate a default offset (to display |
| // last 'count' number of instructions) |
| if (!offset_provided) |
| offset = count - 1; |
| |
| // Get the instruction log |
| ptdecoder::PTInstructionList insn_list; |
| pt_decoder_sp->GetInstructionLogAtOffset(process, thread_id, offset, |
| count, insn_list, error); |
| if (!error.Success()) { |
| res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 ", error: %s", |
| thread.GetIndexID(), thread_id, error.GetCString()); |
| result.AppendMessage(res.GetOutput()); |
| continue; |
| } |
| |
| // Disassemble the instruction log |
| std::string disassembler_command("dis -c 1 -s "); |
| res.Printf("thread #%" PRIu32 ": tid=%" PRIu64 "\n", thread.GetIndexID(), |
| thread_id); |
| lldb::SBCommandInterpreter sb_cmnd_interpreter( |
| debugger.GetCommandInterpreter()); |
| lldb::SBCommandReturnObject result_obj; |
| for (size_t i = 0; i < insn_list.GetSize(); i++) { |
| ptdecoder::PTInstruction insn = insn_list.GetInstructionAtIndex(i); |
| uint64_t addr = insn.GetInsnAddress(); |
| std::string error = insn.GetError(); |
| if (!error.empty()) { |
| res.AppendMessage(error.c_str()); |
| continue; |
| } |
| |
| result_obj.Clear(); |
| std::string complete_disassembler_command = |
| disassembler_command + std::to_string(addr); |
| sb_cmnd_interpreter.HandleCommand(complete_disassembler_command.c_str(), |
| result_obj, false); |
| std::string result_str(result_obj.GetOutput()); |
| if (result_str.empty()) { |
| lldb::SBCommandReturnObject output; |
| output.Printf(" Disassembly not found for address: %" PRIu64, addr); |
| res.AppendMessage(output.GetOutput()); |
| continue; |
| } |
| |
| // LLDB's disassemble command displays assembly instructions along with |
| // the names of the functions they belong to. Parse this result to |
| // display only the assembly instructions and not the function names |
| // in an instruction log |
| std::size_t first_new_line_index = result_str.find_first_of('\n'); |
| std::size_t last_new_line_index = result_str.find_last_of('\n'); |
| if (first_new_line_index != last_new_line_index) |
| res.AppendMessage((result_str.substr(first_new_line_index + 1, |
| last_new_line_index - |
| first_new_line_index - 1)) |
| .c_str()); |
| else |
| res.AppendMessage( |
| (result_str.substr(0, result_str.length() - 1)).c_str()); |
| } |
| result.AppendMessage(res.GetOutput()); |
| } |
| return true; |
| } |
| |
| private: |
| std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp; |
| const uint32_t m_default_count = 10; |
| }; |
| |
| class ProcessorTraceStop : public lldb::SBCommandPluginInterface { |
| public: |
| ProcessorTraceStop(std::shared_ptr<ptdecoder::PTDecoder> &pt_decoder) |
| : SBCommandPluginInterface(), pt_decoder_sp(pt_decoder) {} |
| |
| ~ProcessorTraceStop() {} |
| |
| virtual bool DoExecute(lldb::SBDebugger debugger, char **command, |
| lldb::SBCommandReturnObject &result) { |
| lldb::SBProcess process; |
| lldb::SBThread thread; |
| if (!GetProcess(debugger, result, process)) |
| return false; |
| |
| lldb::tid_t thread_id; |
| |
| // Parse command line options |
| bool thread_argument_provided = false; |
| if (command) { |
| for (uint32_t i = 0; command[i]; i++) { |
| thread_argument_provided = true; |
| if (!ParseCommandArgThread(command, result, process, i, thread_id)) |
| return false; |
| } |
| } |
| |
| if (!thread_argument_provided) { |
| thread = process.GetSelectedThread(); |
| if (!thread.IsValid()) { |
| result.Printf("error: invalid current selected thread\n"); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| thread_id = thread.GetThreadID(); |
| } |
| |
| // Stop trace |
| lldb::SBError error; |
| pt_decoder_sp->StopProcessorTrace(process, error, thread_id); |
| if (!error.Success()) { |
| result.Printf("error: %s\n", error.GetCString()); |
| result.SetStatus(lldb::eReturnStatusFailed); |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| std::shared_ptr<ptdecoder::PTDecoder> pt_decoder_sp; |
| }; |
| |
| bool PTPluginInitialize(lldb::SBDebugger &debugger) { |
| lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); |
| lldb::SBCommand proc_trace = interpreter.AddMultiwordCommand( |
| "processor-trace", "Intel(R) Processor Trace for thread/process"); |
| |
| std::shared_ptr<ptdecoder::PTDecoder> PTDecoderSP( |
| new ptdecoder::PTDecoder(debugger)); |
| |
| lldb::SBCommandPluginInterface *proc_trace_start = |
| new ProcessorTraceStart(PTDecoderSP); |
| const char *help_proc_trace_start = "start Intel(R) Processor Trace on a " |
| "specific thread or on the whole process"; |
| const char *syntax_proc_trace_start = |
| "processor-trace start <cmd-options>\n\n" |
| "\rcmd-options Usage:\n" |
| "\r processor-trace start [-b <buffer-size>] [<thread-index>]\n\n" |
| "\t\b-b <buffer-size>\n" |
| "\t size of the trace buffer to store the trace data. If not " |
| "specified then a default value will be taken\n\n" |
| "\t\b<thread-index>\n" |
| "\t thread index of the thread. If no threads are specified, " |
| "currently selected thread is taken.\n" |
| "\t Use the thread-index 'all' to start tracing the whole process\n"; |
| proc_trace.AddCommand("start", proc_trace_start, help_proc_trace_start, |
| syntax_proc_trace_start); |
| |
| lldb::SBCommandPluginInterface *proc_trace_stop = |
| new ProcessorTraceStop(PTDecoderSP); |
| const char *help_proc_trace_stop = |
| "stop Intel(R) Processor Trace on a specific thread or on whole process"; |
| const char *syntax_proc_trace_stop = |
| "processor-trace stop <cmd-options>\n\n" |
| "\rcmd-options Usage:\n" |
| "\r processor-trace stop [<thread-index>]\n\n" |
| "\t\b<thread-index>\n" |
| "\t thread index of the thread. If no threads are specified, " |
| "currently selected thread is taken.\n" |
| "\t Use the thread-index 'all' to stop tracing the whole process\n"; |
| proc_trace.AddCommand("stop", proc_trace_stop, help_proc_trace_stop, |
| syntax_proc_trace_stop); |
| |
| lldb::SBCommandPluginInterface *proc_trace_show_instr_log = |
| new ProcessorTraceShowInstrLog(PTDecoderSP); |
| const char *help_proc_trace_show_instr_log = |
| "display a log of assembly instructions executed for a specific thread " |
| "or for the whole process.\n" |
| "The length of the log to be displayed and the offset in the whole " |
| "instruction log from where the log needs to be displayed can also be " |
| "provided. The offset is counted from the end of this whole " |
| "instruction log which means the last executed instruction is at offset " |
| "0 (zero)"; |
| const char *syntax_proc_trace_show_instr_log = |
| "processor-trace show-instr-log <cmd-options>\n\n" |
| "\rcmd-options Usage:\n" |
| "\r processor-trace show-instr-log [-o <offset>] [-c <count>] " |
| "[<thread-index>]\n\n" |
| "\t\b-o <offset>\n" |
| "\t offset in the whole instruction log from where the log will be " |
| "displayed. If not specified then a default value will be taken\n\n" |
| "\t\b-c <count>\n" |
| "\t number of instructions to be displayed. If not specified then a " |
| "default value will be taken\n\n" |
| "\t\b<thread-index>\n" |
| "\t thread index of the thread. If no threads are specified, " |
| "currently selected thread is taken.\n" |
| "\t Use the thread-index 'all' to show instruction log for all the " |
| "threads of the process\n"; |
| proc_trace.AddCommand("show-instr-log", proc_trace_show_instr_log, |
| help_proc_trace_show_instr_log, |
| syntax_proc_trace_show_instr_log); |
| |
| lldb::SBCommandPluginInterface *proc_trace_options = |
| new ProcessorTraceInfo(PTDecoderSP); |
| const char *help_proc_trace_show_options = |
| "display all the information regarding Intel(R) Processor Trace for a " |
| "specific thread or for the whole process.\n" |
| "The information contains trace buffer size and configuration options" |
| " of Intel(R) Processor Trace."; |
| const char *syntax_proc_trace_show_options = |
| "processor-trace show-options <cmd-options>\n\n" |
| "\rcmd-options Usage:\n" |
| "\r processor-trace show-options [<thread-index>]\n\n" |
| "\t\b<thread-index>\n" |
| "\t thread index of the thread. If no threads are specified, " |
| "currently selected thread is taken.\n" |
| "\t Use the thread-index 'all' to display information for all threads " |
| "of the process\n"; |
| proc_trace.AddCommand("show-trace-options", proc_trace_options, |
| help_proc_trace_show_options, |
| syntax_proc_trace_show_options); |
| |
| return true; |
| } |