//===-- 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 #include #include #include #include #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 &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 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 &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 pt_decoder_sp; }; class ProcessorTraceShowInstrLog : public lldb::SBCommandPluginInterface { public: ProcessorTraceShowInstrLog(std::shared_ptr &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 pt_decoder_sp; const uint32_t m_default_count = 10; }; class ProcessorTraceStop : public lldb::SBCommandPluginInterface { public: ProcessorTraceStop(std::shared_ptr &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 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 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 \n\n" "\rcmd-options Usage:\n" "\r processor-trace start [-b ] []\n\n" "\t\b-b \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\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 \n\n" "\rcmd-options Usage:\n" "\r processor-trace stop []\n\n" "\t\b\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 \n\n" "\rcmd-options Usage:\n" "\r processor-trace show-instr-log [-o ] [-c ] " "[]\n\n" "\t\b-o \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 \n" "\t number of instructions to be displayed. If not specified then a " "default value will be taken\n\n" "\t\b\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 \n\n" "\rcmd-options Usage:\n" "\r processor-trace show-options []\n\n" "\t\b\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; }