diff --git a/README.md b/README.md index 6c3589a..77afe82 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Vebugger is built as a generic framework for building frontends for interactive shell debugger, and comes with implementations for: * GDB - doesn't need introdcution... + * LLDB - debugger based on LLVM for C-family languages * JDB - a Java debugger * Mdbg - a .NET debugger(Windows only) * PDB - a Python module for debugging Python scripts diff --git a/autoload/vebugger/lldb.vim b/autoload/vebugger/lldb.vim new file mode 100644 index 0000000..da77faa --- /dev/null +++ b/autoload/vebugger/lldb.vim @@ -0,0 +1,139 @@ +let s:script_dir_path=expand(':p:h') + +function! vebugger#lldb#start(binaryFile,args) + let l:debuggerExe=vebugger#util#getToolFullPath('python','lldb','python2') + let l:debugger=vebugger#std#startDebugger(shellescape(l:debuggerExe) + \.' '.s:script_dir_path.'/lldb_wrapper.py '.fnameescape(a:binaryFile)) + + let l:debugger.state.lldb={} + + if get(a:args,'pid') "Attach to process + call l:debugger.writeLine('process attach --pid '.string(a:args.pid)) + elseif has_key(a:args,'con') "Attach to lldbserver + call l:debugger.writeLine('platform connect connect://'.a:args.con) + else + call l:debugger.writeLine('settings set target.run-args '.vebugger#util#commandLineArgsForProgram(a:args)) + if !has('win32') + call vebugger#std#openShellBuffer(l:debugger) + endif + + " TODO: remove 'and false'; add a temporary breakpoint to lldb + if has_key(a:args,'entry') && 0 + " call l:debugger.writeLine('tbreak '.a:args.entry) + " call l:debugger.writeLine('run') + else + call l:debugger.writeLine('breakpoint set --name main') + call l:debugger.writeLine('process launch') + endif + endif + + + call l:debugger.addReadHandler(function('vebugger#lldb#_readProgramOutput')) + call l:debugger.addReadHandler(function('vebugger#lldb#_readWhere')) + call l:debugger.addReadHandler(function('vebugger#lldb#_readFinish')) + call l:debugger.addReadHandler(function('vebugger#lldb#_readEvaluatedExpressions')) + + call l:debugger.setWriteHandler('std','flow',function('vebugger#lldb#_writeFlow')) + call l:debugger.setWriteHandler('std','breakpoints',function('vebugger#lldb#_writeBreakpoints')) + call l:debugger.setWriteHandler('std','closeDebugger',function('vebugger#lldb#_closeDebugger')) + call l:debugger.setWriteHandler('std','evaluateExpressions',function('vebugger#lldb#_requestEvaluateExpression')) + call l:debugger.setWriteHandler('std','executeStatements',function('vebugger#lldb#_executeStatements')) + + call l:debugger.generateWriteActionsFromTemplate() + + call l:debugger.std_addAllBreakpointActions(g:vebugger_breakpoints) + + return l:debugger +endfunction + +function! vebugger#lldb#_readProgramOutput(pipeName,line,readResult,debugger) + if 'out'==a:pipeName + \&&(a:line=~'\v^program_stdout:' + \||a:line=~'\v^program_stderr:') + let a:readResult.std.programOutput={'line':strpart(a:line, 16)} + endif +endfunction + +function! vebugger#lldb#_readWhere(pipeName,line,readResult,debugger) + if 'out'==a:pipeName + \&&a:line=~'\v^where:' + let l:matches=matchlist(a:line,'\v^where:\s([^:]+):(\d+)') + if 2 first read lldbinit + try: + with open(os.path.expanduser('~/.lldbinit')) as f: + for line in f: + self.run_command(line) + except IOError: + pass + self.run_command('settings set frame-format frame #${frame.index}: ${frame.pc}{ ${module.file.basename}`${function.name}{${function.pc-offset}}}{ at:${line.file.fullpath}:${line.number}}\n') + self.run_command('settings set auto-confirm 1') + + def run_command(self, commandline): + if self._is_custom_command(commandline): + self._run_custom_command(commandline) + else: + if isinstance(commandline, unicode): + commandline = commandline.encode('utf-8') + return_obj = lldb.SBCommandReturnObject() + self._command_interpreter.HandleCommand(commandline, return_obj) + if self._process is None or self._process.GetState() == lldb.eStateInvalid: + self._process = self._command_interpreter.GetProcess() + self._last_debugger_return_obj = return_obj + + def _is_custom_command(self, commandline): + command = commandline.split()[0] + return command in self._custom_commands + + def _run_custom_command(self, commandline): + def br(arguments): + filename, linenumber = arguments[0].split(':') + linenumber = int(linenumber) + self._breakpoint_manager.add_breakpoint(filename, linenumber) + self.run_command('b {}'.format(*arguments)) + + def clear(arguments): + filename, linenumber = arguments[0].split(':') + linenumber = int(linenumber) + breakpoint_id = self._breakpoint_manager.remove_breakpoint(filename, + linenumber) + self.run_command('breakpoint delete {:d}'.format(breakpoint_id)) + + if not self._is_custom_command(commandline): + raise NoCustomCommandError + parts = commandline.split() + print('parts:', parts) + command = parts[0] + arguments = parts[1:] + locals()[command](arguments) + + @property + def debugger_output(self): + if self._last_debugger_return_obj is not None: + return_obj = self._last_debugger_return_obj + self._last_debugger_return_obj = None + return return_obj.GetOutput() + else: + return '' + + @property + def where(self): + def extract_where(backtrace): + where = None + pattern = re.compile('at:([^:]+):(\d+)') + backtrace_lines = backtrace.split('\n') + for line in backtrace_lines: + match_obj = pattern.search(line) + if match_obj: + filepath = match_obj.group(1) + linenumber = int(match_obj.group(2)) + if os.access(filepath, os.R_OK): + where = FilePosition(filepath, linenumber) + break + return where + + where = None + self.run_command('bt') + debugger_output = self.debugger_output + if debugger_output is not None: + where = extract_where(debugger_output) + return where + + @property + def program_stdout(self): + stdout = [] + has_text = True + while has_text: + text = self._process.GetSTDOUT(1024) + if text: + stdout.append(text) + else: + has_text = False + return ''.join(stdout) + + @property + def program_stderr(self): + stderr = [] + has_text = True + while has_text: + text = self._process.GetSTDERR(1024) + if text: + stderr.append(text) + else: + has_text = False + return ''.join(stderr) + + @property + def program_state(self): + return self._state_id_to_name(self._process.GetState()) + + def _state_id_to_name(self, state_id): + if self._state_dict is None: + self._state_dict = {} + for key, value in lldb.__dict__.iteritems(): + if key.startswith('eState'): + self._state_dict[value] = key[6:] + return self._state_dict[state_id] + + +def prefix_output(output, prefix): + lines = output.split('\n') + lines = [prefix + line for line in lines] + prefixed_output = '\n'.join(lines) + return prefixed_output + + +def main(): + if len(sys.argv) < 2: + sys.stderr.write('An executable is needed as an argument.\n') + sys.exit(1) + + executable = sys.argv[1] + debugger = Debugger(executable) + + try: + while True: + line = raw_input() + # TODO: find a way to check directly if the debugger was terminated + if line in ['exit', 'quit']: + raise EOFError + debugger.run_command(line) + program_stdout = debugger.program_stdout + if program_stdout: + print(prefix_output(program_stdout, 'program_stdout: ')) + program_stderr = debugger.program_stderr + if program_stderr: + print(prefix_output(program_stderr, 'program_stderr: ')) + print(prefix_output(debugger.debugger_output, 'debugger_output: ')) + where = debugger.where + if where: + print(prefix_output('{:s}:{:d}'.format(where.filepath, where.linenumber), 'where: ')) + print(prefix_output(debugger.program_state, 'program_state: ')) + except EOFError: + print('Exiting') + + +if __name__ == '__main__': + main() diff --git a/doc/vebugger.txt b/doc/vebugger.txt index 7f781a5..5a7eeb5 100644 --- a/doc/vebugger.txt +++ b/doc/vebugger.txt @@ -20,6 +20,7 @@ supports: Vebugger is built as a generic framework for building frontends for interactive shell debugger, and comes with implementations for: * GDB - doesn't need introdcution... +* LLDB - debugger based on LLVM for C-family languages * JDB - a Java debugger * Mdbg - a .NET debugger(Windows only) * PDB - a Python module for debugging Python scripts @@ -76,24 +77,32 @@ Example: > If a debugger is not in the PATH you can set the direct path to it by setting g:vebugger_path_XXX, where XXX is the executable used for the debugger: -*g:vebugger_path_gdb* defaults to "gdb" -*g:vebugger_path_jdb* defaults to "jdb" -*g:vebugger_path_mdbg* defaults to "Mdbg.exe" -*g:vebugger_path_python* defaults to "python" -*g:vebugger_path_ruby* defaults to "ruby" +*g:vebugger_path_gdb* defaults to "gdb" +*g:vebugger_path_python_lldb* defaults to "python2" +*g:vebugger_path_jdb* defaults to "jdb" +*g:vebugger_path_mdbg* defaults to "Mdbg.exe" +*g:vebugger_path_python* defaults to "python" +*g:vebugger_path_ruby* defaults to "ruby" -Notice that for PDB and RDebug you use "python" and "ruby", since the debugger -is actually a module bundled in the interpreter. +Notice that for LLDB, PDB and RDebug you use "python_lldb", "python" and +"ruby", since the debugger is actually a module bundled in the interpreter +(LLDB is called by a python wrapper because the LLDB executable has no +machine interface like GDB to interact with). -You can set multiple versions for each debugger, by appending the version name -to the debugger name with "_". These versions will be used when the "version" -argument is supplied when running the debugger: +You can set multiple versions for each debugger (except LLDB), by appending +the version name to the debugger name with "_". These versions will be used +when the "version" argument is supplied when running the debugger: *g:vebugger_path_python_2* defaults to "python2" *g:vebugger_path_python_3* defaults to "python3" *g:vebugger_path_mdbg_32* No default - use it for the 32bit version of Mdbg *g:vebugger_path_mdbg_64* No default - use it for the 64bit version of Mdbg +For LLDB a special python variable is needed ("g:vebugger_path_python_lldb") +since the lldb python api is only python 2 compatible. So, it is possible to +use a python 2 interpreter for LLDB and another python version for PDB at +the same time. + When stepping into functions in unopened files, they are opened with :new by default. This can be configured by setting *g:vebugger_view_source_cmd* @@ -153,6 +162,33 @@ attach to or the URL for a GDB-server to connect to. The *VBGstartGDBForD* command is the same as VBGstartGDB but for Dlang programs. + +LAUNCHING LLDB *vebugger-lldb* + +LLDB can be launched with *vebugger#lldb#start* +> + call vebugger#lldb#start('a.out',{'args':['hello','world']}) +< + +The supported extra arguments are: +* "args": Command line arguments for the debugged program +* "pid": Process id to attach to +You can't specify both "args" and "pid". + +LLDB can also be launched with the *VBGstartLLDB* command: +> + VBGstartLLDB a.out hello world +< +The *VBGattachLLDB* command searches for processes launched from the EXE to +attach to, and attaches to them: +> + VBGattachLLDB a.out +< + +VBGattachLLDB accepts as a second argument the process ID of the process to +attach to. + + LAUNCHING JDB *vebugger-jdb* JDB is launched with *vebugger#jdb#start* diff --git a/plugin/vebugger.vim b/plugin/vebugger.vim index ab51944..451b428 100644 --- a/plugin/vebugger.vim +++ b/plugin/vebugger.vim @@ -41,6 +41,24 @@ function! s:attachGDB(...) endif endfunction command! -nargs=+ -complete=file VBGattachGDB call s:attachGDB() +command! -nargs=+ -complete=file VBGstartLLDB call vebugger#lldb#start([][0],{'args':[][1:]}) +function! s:attachLLDB(...) + if 1 == a:0 + let l:processId=vebugger#util#selectProcessOfFile(a:1) + if 0 < l:processId + call vebugger#lldb#start(a:1, {'pid': l:processId}) + endif + elseif 2 == a:0 + if a:2 =~ '\v^\d+$' + call vebugger#lldb#start(a:1,{'pid': str2nr(a:2)}) + else + call vebugger#lldb#start(a:1, {'con': a:2}) + endif + else + throw "Can't call VBGattachLLDB with ".a:0." arguments" + endif +endfunction +command! -nargs=+ -complete=file VBGattachLLDB call s:attachLLDB() command! -nargs=+ -complete=file VBGstartRDebug call vebugger#rdebug#start([][0],{'args':[][1:]}) command! -nargs=+ -complete=file VBGstartPDB call vebugger#pdb#start([][0],{'args':[][1:]}) command! -nargs=+ -complete=file VBGstartPDB2 call vebugger#pdb#start([][0],{'args':[][1:],'version':'2'})