diff --git a/README.md b/README.md index 955608c..49549a3 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,13 @@ supports: * Debugger flow commands - step-in, set-over, set-out and continue * Breakpoints management * Evaluating expressions in the current executed scope - * Messing with the program's state(changing values, calling functions) + * Messing with the program's state (changing values, calling functions) Vebugger is built as a generic framework for building frontends for interactive shell debugger, and comes with implementations for: - * GDB - doesn't need introdcution... + * GDB - doesn't need introduction... + * 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.vim b/autoload/vebugger.vim index 795431b..3a29b54 100644 --- a/autoload/vebugger.vim +++ b/autoload/vebugger.vim @@ -1,199 +1,199 @@ -"Read and return all new lines from a Vebugger pipe object. -function! s:readNewLinesFromPipe(pipeObject) - "read - let l:text=a:pipeObject.pipe.read(1000,0) - while 0&2') if !has('win32') @@ -62,6 +60,61 @@ function! s:findFolderFromStackTrace(src,nameFromStackTrace) return l:path endfunction +function! vebugger#gdb#readMILinesFromPipe() dict + if !has_key(self, 'tildeBuffer') + let self.tildeBuffer = '' + endif + let l:result = [] + + while 1 + let l:newLinePos = stridx(self.buffer, "\n") + if l:newLinePos < 0 + break + endif + let l:firstChar = self.buffer[0] + if l:firstChar == '~' + while self.buffer[0] == '~' + let l:newLinePos = stridx(self.buffer, "\n") + let l:line = self.buffer[0 : l:newLinePos] + let l:line = l:line[2 : strridx(l:line, '"') - 1] + let self.buffer = strpart(self.buffer, l:newLinePos + 1) + "let self.tildeBuffer .= l:line + let l:line = substitute(l:line, '\\n', "\n", "g") + let l:line = substitute(l:line, '\\t', "\t", "g") + let l:line = substitute(l:line, '\\"', '"', "g") + let self.tildeBuffer .= l:line + endwhile + let l:lastTildeNewLine = strridx(self.tildeBuffer, "\n") + if 0 <= l:lastTildeNewLine + let l:newTildeLines = split(self.tildeBuffer[: l:lastTildeNewLine - 1], '\n') + call map(l:newTildeLines, '"~".v:val') + let self.tildeBuffer = strpart(self.tildeBuffer, l:lastTildeNewLine + 1) + call extend(l:result, l:newTildeLines) + endif + continue + else + if !empty(self.tildeBuffer) + call extend(l:result, split(self.tildeBuffer, '\n')) + let self.tildeBuffer = '' + endif + endif + let l:line = self.buffer[0 : l:newLinePos] + if l:firstChar == '&' + let l:line = l:line[2 : strridx(l:line, '"') - 1] + let l:line = substitute(l:line, '\\n', "\n", "g") + let l:line = substitute(l:line, '\\t', "\t", "g") + let l:line = substitute(l:line, '\\"', '"', "g") + let l:line = '&'.l:line + endif + + let l:line = substitute(l:line, '\v[\n\r]+$', '', "") + call add(l:result, l:line) + let self.buffer = strpart(self.buffer, l:newLinePos + 1) + endwhile + + return l:result +endfunction + function! vebugger#gdb#_readProgramOutput(pipeName,line,readResult,debugger) if 'err'==a:pipeName \&&a:line!~'\v^[=~*&^]' @@ -72,11 +125,10 @@ endfunction function! vebugger#gdb#_readWhere(pipeName,line,readResult,debugger) if 'out'==a:pipeName - "let l:matches=matchlist(a:line,'\v^\~"#(\d+)\s+(.+)\s+\(.*\)\s+at\s+([^:]+):(\d+)') let l:matches=matchlist(a:line,'\v^\*stopped.*fullname\=\"([^"]+)\",line\=\"(\d+)"') if 2 \>' @@ -56,15 +67,46 @@ function! vebugger#jdb#_readProgramOutput(pipeName,line,readResult,debugger) dic endif endfunction -function! s:findFolderFromStackTrace(src,nameFromStackTrace) - let l:path=a:src - for l:dirname in split(a:nameFromStackTrace,'\.') - let l:nextPath=l:path.'/'.fnameescape(l:dirname) - if empty(glob(l:nextPath)) - return l:path - endif - let l:path=l:nextPath - endfor +function! s:getTagContainingString(tag, str) + let l:tags = taglist(a:tag) + if (len(l:tags) > 0) + for l:tag in l:tags + if (filereadable(l:tag.filename) && match(readfile(l:tag.filename), a:str) >= 0) + return l:tag + endif + endfor + endif + return {} +endfunction + +function! s:findFolderFromStackTrace(src,nameFromStackTrace,frameNumber) + " Remove method name. + let l:canonicalClassName = strpart(a:nameFromStackTrace, 0, strridx(a:nameFromStackTrace, ".")) + " Remove package name. + let l:simpleClassName = strridx(l:canonicalClassName, ".") >= 0 ? strpart(l:canonicalClassName, strridx(l:canonicalClassName, ".") + 1) : l:canonicalClassName + " Remove class name. + let l:package = strridx(l:canonicalClassName, ".") >= 0 ? strpart(l:canonicalClassName, 0, strridx(l:canonicalClassName, ".")) : "" + + " We don't really use callstack, so we use tags only for the current location. + " Otherwise it makes everything too slow. + if exists('g:vebugger_use_tags') && g:vebugger_use_tags && a:frameNumber == 1 + " Now first try to find a tag for the class from the required package. + let l:classTag = s:getTagContainingString(l:simpleClassName, l:package) + if (has_key(l:classTag, "filename")) + return fnamemodify(l:classTag.filename, ":h") + endif + endif + + " If no such tag was found, try to find it using the src path. + for l:path in vebugger#util#listify(a:src) + for l:dirname in split(a:nameFromStackTrace,'\.') + let l:nextPath=l:path.'/'.fnameescape(l:dirname) + if empty(glob(l:nextPath)) + return l:path + endif + let l:path=l:nextPath + endfor + endfor return l:path endfunction @@ -72,9 +114,9 @@ function! vebugger#jdb#_readWhere(pipeName,line,readResult,debugger) if 'out'==a:pipeName let l:matches=matchlist(a:line,'\v\s*\[(\d+)]\s*(\S+)\s*\(([^:]*):(\d*)\)') if 4: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): + if output is None: + output = '' + 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/autoload/vebugger/mdbg.vim b/autoload/vebugger/mdbg.vim index a747bf6..903778a 100644 --- a/autoload/vebugger/mdbg.vim +++ b/autoload/vebugger/mdbg.vim @@ -95,7 +95,7 @@ function! vebugger#mdbg#_readWhere(pipeName,line,readResult,debugger) if 3 (.+)\((\d+)\).*\(\)$') + let l:matches=matchlist(a:line,'\v^\> (.+)\((\d+)\).*\(\)%(-\>.*)?$') if 2 if exists('b:debugger') | call b:debugger.kill() | endif - setlocal buftype=nofile - setlocal bufhidden=wipe - let a:debugger.shellBuffer=bufnr('') - silent file Vebugger:Shell - wincmd p + if has_key(a:debugger,'shellBuffer') + if -1 if exists('b:debugger') | call b:debugger.kill() | endif + setlocal buftype=nofile + setlocal filetype=VebuggerShell + setlocal bufhidden=wipe + let a:debugger.shellBuffer=bufnr('') + silent file Vebugger:Shell + wincmd p +endfunction + +"Closes the shell buffer +function! vebugger#std#closeShellBuffer(debugger) + if has_key(a:debugger,'shellBuffer') + if -1" + if mode() == 'n' + " Call echo normaly + call feedkeys(l:echoKeys) + elseif mode() == 'i' + " Execute command in insert mode + call feedkeys("\".l:echoKeys) + endif + " NOTE: Other modes are not supported + else + " Without timer support, feedkeys won't work and we have + " to echo directly + echo join(l:echoLines, "\n") + endif endfunction "Print an expression that it's evaluation was previously requested function! s:standardThinkHandlers.printEvaluatedExpression(readResult,debugger) dict - let l:evaluatedExpression=a:readResult.std.evaluatedExpression - if !empty(l:evaluatedExpression) - if empty(get(l:evaluatedExpression,'expression')) - echo l:evaluatedExpression.value."\n" - else - let l:index=index(a:debugger.state.std.evaluateExpressions,l:evaluatedExpression.expression) - if 0<=l:index - call remove(a:debugger.state.std.evaluateExpressions,l:index) - echo l:evaluatedExpression.expression.': '.l:evaluatedExpression.value."\n" - let g:echo=l:evaluatedExpression.expression.': '.l:evaluatedExpression.value."\n" - endif - endif - call a:debugger.addWriteAction('std','removeAfterDisplayed',a:readResult) - endif + let l:evaluatedExpression=a:readResult.std.evaluatedExpression + if !empty(l:evaluatedExpression) + if !empty(get(l:evaluatedExpression,'expression')) + let l:index=index(a:debugger.state.std.evaluateExpressions,l:evaluatedExpression.expression) + if 0<=l:index + call remove(a:debugger.state.std.evaluateExpressions,l:index) + endif + endif + call s:printWithFeedKeys(l:evaluatedExpression) + call a:debugger.addWriteAction('std','removeAfterDisplayed',a:readResult) + endif endfunction "Close the debugger when the program is finished but the debugger wasn't "closed automatically function! s:standardThinkHandlers.closeDebuggerWhenProgramFinishes(readResult,debugger) dict - if !empty(a:readResult.std.programFinish) - call a:debugger.setWriteAction('std','closeDebugger','close') - endif + if !empty(a:readResult.std.programFinish) + call a:debugger.setWriteAction('std','closeDebugger','close') + endif endfunction "Print an exception message function! s:standardThinkHandlers.printException(readResult,debugger) dict - if !empty(a:readResult.std.exception) - echohl WarningMsg - echo a:readResult.std.exception.message."\n" - echohl None - endif + if !empty(a:readResult.std.exception) + echohl WarningMsg + echo a:readResult.std.exception.message."\n" + echohl None + endif endfunction let s:standardCloseHandlers={} "Remove the currently executed line when a debugger is closed function! s:standardCloseHandlers.removeCurrentMarker(debugger) dict - let a:debugger.state.std.location={} - sign unplace 1 + let a:debugger.state.std.location={} + sign unplace 1 endfunction -sign define vebugger_current text=-> -sign define vebugger_breakpoint text=** linehl=ColorColumn +let s:breakpoint_text = get(g:, 'vebugger_breakpoint_text', '->') +let s:currentline_text = get(g:, 'vebugger_currentline_text', '**') + +if hlexists("DebuggedLine") + sign define vebugger_current linehl=DebuggedLine +else + execute 'sign define vebugger_current text=' . s:currentline_text +endif + +if hlexists('BreakPoint') + execute 'sign define vebugger_breakpoint text=' . s:breakpoint_text . ' linehl=BreakPoint texthl=BreakPoint' +else + execute 'sign define vebugger_breakpoint text=' . s:breakpoint_text . ' linehl=ColorColumn texthl=ColorColumn' +endif "Update all the marks(currently executed line and breakpoints) for a file function! vebugger#std#updateMarksForFile(state,filename) - let l:filename=fnamemodify(a:filename,":p") - if bufexists(l:filename) - exe 'sign unplace 1 file='.fnameescape(fnamemodify(l:filename,':p')) - exe 'sign unplace 2 file='.fnameescape(fnamemodify(l:filename,':p')) + let l:filename=fnamemodify(a:filename,":p") + let l:bufnr = bufnr(l:filename) + if -1 < l:bufnr + exe 'sign unplace 1 file='.fnameescape(fnamemodify(l:filename,':p')) + for l:sign in vebugger#util#listSignsInBuffer(l:bufnr) + if l:sign.name == 'vebugger_breakpoint' + exe 'sign unplace 2 file='.fnameescape(fnamemodify(l:filename,':p')) + endif + endfor - for l:breakpoint in g:vebugger_breakpoints - if fnamemodify(l:breakpoint.file,':p')==fnamemodify(a:filename,':p') - exe 'sign place 2 name=vebugger_breakpoint line='.l:breakpoint.line.' file='.fnameescape(fnamemodify(l:breakpoint.file,':p')) - endif - endfor + for l:breakpoint in g:vebugger_breakpoints + if fnamemodify(l:breakpoint.file,':p')==fnamemodify(a:filename,':p') + exe 'sign place 2 name=vebugger_breakpoint line='.l:breakpoint.line.' file='.fnameescape(fnamemodify(l:breakpoint.file,':p')) + endif + endfor - if !empty(a:state) - if !empty(a:state.std.location) - if fnamemodify(a:state.std.location.file,':p')==fnamemodify(a:filename,':p') - exe 'sign place 1 name=vebugger_current line='.a:state.std.location.line.' file='.fnameescape(fnamemodify(l:filename,':p')) - endif - endif - endif - endif + if !empty(a:state) + if !empty(a:state.std.location) + if fnamemodify(a:state.std.location.file,':p')==fnamemodify(a:filename,':p') + exe 'sign place 1 name=vebugger_current line='.a:state.std.location.line.' file='.fnameescape(fnamemodify(l:filename,':p')) + endif + endif + endif + endif endfunction "Toggle a breakpoint on and off function! vebugger#std#toggleBreakpoint(file,line) - let l:debugger=vebugger#getActiveDebugger() - let l:debuggerState=empty(l:debugger) - \? {} - \: l:debugger.state - for l:i in range(len(g:vebugger_breakpoints)) - let l:breakpoint=g:vebugger_breakpoints[l:i] - if l:breakpoint.file==a:file && l:breakpoint.line==a:line - call remove(g:vebugger_breakpoints,l:i) - call vebugger#addWriteActionAndPerform('std','breakpoints',{ - \'action':'remove', - \'file':(a:file), - \'line':(a:line)}) - call vebugger#std#updateMarksForFile(l:debuggerState,a:file) - return - endif - endfor - call add(g:vebugger_breakpoints,{'file':(a:file),'line':(a:line)}) - call vebugger#addWriteActionAndPerform('std','breakpoints',{ - \'action':'add', - \'file':(a:file), - \'line':(a:line)}) - call vebugger#std#updateMarksForFile(l:debuggerState,a:file) + let l:debugger=vebugger#getActiveDebugger() + let l:debuggerState=empty(l:debugger) + \? {} + \: l:debugger.state + for l:i in range(len(g:vebugger_breakpoints)) + let l:breakpoint=g:vebugger_breakpoints[l:i] + if l:breakpoint.file==a:file && l:breakpoint.line==a:line + call remove(g:vebugger_breakpoints,l:i) + if !empty(l:debugger) + call l:debugger.addWriteActionAndPerform('std','breakpoints',{ + \'action':'remove', + \'file':(a:file), + \'line':(a:line)}) + endif + call vebugger#std#updateMarksForFile(l:debuggerState,a:file) + return + endif + endfor + call add(g:vebugger_breakpoints,{'file':(a:file),'line':(a:line)}) + if !empty(l:debugger) + call l:debugger.addWriteActionAndPerform('std','breakpoints',{ + \'action':'add', + \'file':(a:file), + \'line':(a:line)}) + endif + call vebugger#std#updateMarksForFile(l:debuggerState,a:file) endfunction "Clear all breakpoints function! vebugger#std#clearBreakpoints() - let l:debugger=vebugger#getActiveDebugger() - let l:debuggerState=empty(l:debugger) ? {} : l:debugger.state - let l:files=[] - for l:breakpoint in g:vebugger_breakpoints - if index(l:files,l:breakpoint.file)<0 - call add(l:files,l:breakpoint.file) - endif - call vebugger#addWriteAction('std','breakpoints',extend({'action':'remove'},l:breakpoint)) - endfor - call vebugger#performWriteActions() - let g:vebugger_breakpoints=[] - for l:file in l:files - call vebugger#std#updateMarksForFile(l:debuggerState,l:file) - endfor -endfunction - -"Ask the active debugger to evaluate an expression -function! vebugger#std#eval(expression) - let l:debugger=vebugger#getActiveDebugger() - if !empty(l:debugger) && !empty(l:debugger.std_eval) - call l:debugger.std_eval(a:expression) - endif -endfunction - -"Ask the active debugger to execute a statement -function! vebugger#std#execute(statement) - let l:debugger=vebugger#getActiveDebugger() - if !empty(l:debugger) && !empty(l:debugger.std_eval) - call l:debugger.std_execute(a:statement) - endif + let l:debugger=vebugger#getActiveDebugger() + let l:debuggerState=empty(l:debugger) ? {} : l:debugger.state + let l:files=[] + for l:breakpoint in g:vebugger_breakpoints + if index(l:files,l:breakpoint.file)<0 + call add(l:files,l:breakpoint.file) + endif + if !empty(l:debugger) + call l:debugger.addWriteAction('std','breakpoints',extend({'action':'remove'},l:breakpoint)) + endif + endfor + if !empty(l:debugger) + call l:debugger.performWriteActions() + endif + let g:vebugger_breakpoints=[] + for l:file in l:files + call vebugger#std#updateMarksForFile(l:debuggerState,l:file) + endfor endfunction diff --git a/autoload/vebugger/util.vim b/autoload/vebugger/util.vim index 3c1a5f9..1c7f33e 100644 --- a/autoload/vebugger/util.vim +++ b/autoload/vebugger/util.vim @@ -57,7 +57,8 @@ function! vebugger#util#selectProcessOfFile(ofFile) return str2nr(l:resultLinesParsed[l:chosenId][1]) else let l:chosenLine=l:resultLines[l:chosenId] - return str2nr(matchlist(l:chosenLine,'\v^\s*\d+\)\s+(\d+)')[1]) + let g:chosenLine = l:chosenLine + return str2nr(matchstr(l:chosenLine,'\v^\s*\zs(\d+)\ze\s*')) endif endfunction @@ -114,3 +115,34 @@ function! vebugger#util#isPathAbsolute(path) return a:path[0]=~'\v^[/~$]' "Absolute paths in Linux start with ~(home),/(root dir) or $(environment variable) endif endfunction + +function! vebugger#util#listify(stringOrList) + if type(a:stringOrList) == type([]) + return copy(a:stringOrList) " so it could safely be modified by map&filter + else + return [a:stringOrList] + endif +endfunction + +function! s:listSigns(filter) abort + let l:result = [] + for l:line in split(execute('sign place '.a:filter), '\n') + let l:match = matchlist(l:line, '\C\v^\s+line\=(\d+)\s+id\=(\d+)\s+name\=(.+)$') + if !empty(l:match) + call add(l:result, { + \ 'line': str2nr(l:match[1]), + \ 'id': str2nr(l:match[2]), + \ 'name': l:match[3], + \ }) + endif + endfor + return l:result +endfunction + +function! vebugger#util#listSignsInBuffer(bufnr) abort + return s:listSigns('buffer='.a:bufnr) +endfunction + +function! vebugger#util#listSignsInFile(filename) abort + return s:listSigns('file='.a:filename) +endfunction diff --git a/doc/vebugger.txt b/doc/vebugger.txt index f268420..cee6412 100644 --- a/doc/vebugger.txt +++ b/doc/vebugger.txt @@ -4,7 +4,7 @@ Author: Idan Arye License: Same terms as Vim itself (see |license|) -Version: 1.2.3 +Version: 1.2.3+ INTRODUCTION *vebugger* @@ -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 @@ -77,25 +78,56 @@ 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_node* defaults to "node inspect" +*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" +*g:vebugger_path_node* defaults to "node inspect" -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* + +Example: > + let g:vebugger_view_source_cmd='edit' +< + +If you want to change the sign text of current line and breakpoint, use +*g:vebugger_breakpoint_text* and *g:vebugger_currentline_text* + +Example: > + let g:vebugger_breakpoint_text='->' + let g:vebugger_currentline_text='**' +< + +Some debuggers (currently jdb only) may use vim tags to find required source +files. This is disabled by default, to enable this set *g:vebugger_use_tags* +option: + +Example: > + let g:vebugger_use_tags=1 +< + LAUNCHING DEBUGGERS *vebugger-launching* A debugger's implementation is responsible for starting it. The standard is to @@ -119,6 +151,7 @@ GDB can be launched with *vebugger#gdb#start* The supported extra arguments are: * "args": Command line arguments for the debugged program * "pid": Process id to attach to +* "con": URL of GDB-server to connect to * "entry": The entry point for starting the debugging(default "main") * "version": The version of the debugger to run You can't specify both ("args" and/or "entry") and "pid". @@ -132,9 +165,40 @@ attach to, and attaches to them: > VBGattachGDB a.out < + +VBGattachGDB accepts as a second argument the process ID of the process to +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* @@ -148,14 +212,27 @@ Unlike in the other debuggers, the first argument here is not the name of a file - it's the name of the class to run. The supported extra arguments are: * "args": Command line arguments for the debugged program * "classpath": Where to look for class files -* "srcpath": Where to look for source files +* "srcpath": Where to look for source files. You can have multiple srcpaths, passed as a list * "version": The version of the debugger to run + +Alternatively, to attach to a running process you can use *vebugger#jdb#attach* +> + call vebugger#jdb#attach('8000',{ + \'srcpath':'src'}) +< +The first argument here is the the address to attach to, e.g. 127.0.0.1:8000 +or just 8000. It does not support the "args" or "classpath" arguments. + If you don't supply "classpath" and "srcpath", Vebugger will assume you are using the current directory for source files and class files. JDB does not have a command for starting it, since you usually want to supply "classpath" and "srcpath". +If you need Vebugger to search for the source files in multiple locations, you can +specify `srcpath` as a list. Vebugger will search on all these folders +and the first match will be returned as the Source file to be shown. + LAUNCHING RDEBUG *vebugger-rdebug* @@ -258,7 +335,7 @@ MANAGE BREAKPOINTS *vebugger-breakpoints* *:VBGtoggleBreakpoint* Toggle a breakpoint. The file and line should be supplied as arguments. *:VBGtoggleBreakpointThisLine* Toggle a breakpoint for the current line. -*:VBGclearBreakpints* Clear all breakpoints. +*:VBGclearBreakpoints* Clear all breakpoints. EVALUATE EXPRESSIONS *vebugger-evalutate* @@ -299,7 +376,7 @@ O |:VBGstepOut| c |:VBGcontinue| b |:VBGtoggleBreakpointThisLine| -B |:VBGclearBreakpints| +B |:VBGclearBreakpoints| e |:VBGevalWordUnderCursor| in normal mode |:VBGevalSelectedText| in select mode @@ -490,3 +567,26 @@ defines the "std" namespace, and plugins built on top of Vebugger can define a namespaced named after the plugin to contain their own read results. A debugger frontend implementation can also define it's own namespace for storing debugger-specific data and configuration. + +COMPILER OUTPUT BUFFERERS *vebugger-architecture-bufferers* + +Read handlers receive lines created by the debugger output bufferer. There is +only one for each debugger, and if you just want to get the debugger output +line-by-line "as is" you can just use the default one and ignore this section. + +The debugger bufferer is a dict function(|:func-dict|) that operates on pipe +objects. It will be called when the pipe object's "self.buffer", which +represents everything read from a debugger's output stream, has new data. + +The bufferer function should: +* return a list of new lines that will be send to the read handlers, or an + empty list if there are no new lines. +* "advance" the buffer - delete from it's head everything that was handled. + This can easily be done with |strpart()|. + +Bufferers should be set individually for each pipe, usually in the debugger +creation function: + +Example: > + let l:debugger.pipes.out.bufferer = function('g:myCustomBufferer') +< diff --git a/plugin/vebugger.vim b/plugin/vebugger.vim index bb53086..089968e 100644 --- a/plugin/vebugger.vim +++ b/plugin/vebugger.vim @@ -1,28 +1,64 @@ -command! -nargs=1 VBGrawWrite call vebugger#writeLine() +command! -nargs=0 VBGrepeat call vebugger#repeatLastUserAction() + +command! -nargs=1 VBGrawWrite call vebugger#userAction('writeLine', ) command! -nargs=0 VBGkill call vebugger#killDebugger() -command! -nargs=0 VBGstepIn call vebugger#setWriteActionAndPerform('std','flow','stepin') -command! -nargs=0 VBGstepOver call vebugger#setWriteActionAndPerform('std','flow','stepover') -command! -nargs=0 VBGstepOut call vebugger#setWriteActionAndPerform('std','flow','stepout') -command! -nargs=0 VBGcontinue call vebugger#setWriteActionAndPerform('std','flow','continue') +command! -nargs=0 VBGstepIn call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'stepin') +command! -nargs=0 VBGstepOver call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'stepover') +command! -nargs=0 VBGstepOut call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'stepout') +command! -nargs=0 VBGcontinue call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'continue') -command! -nargs=0 VBGtoggleTerminalBuffer call vebugger#toggleTerminalBuffer() +command! -nargs=0 VBGtoggleTerminalBuffer call vebugger#userAction('toggleTerminalBuffer') command! -nargs=+ -complete=file VBGtoggleBreakpoint call vebugger#std#toggleBreakpoint() -command! -nargs=0 VBGtoggleBreakpointThisLine call vebugger#std#toggleBreakpoint(expand('%:~:.'),line('.')) -command! -nargs=0 VBGclearBreakpints call vebugger#std#clearBreakpoints() +command! -nargs=0 VBGtoggleBreakpointThisLine call vebugger#std#toggleBreakpoint(expand('%:p:.'),line('.')) +command! -nargs=0 VBGclearBreakpoints call vebugger#std#clearBreakpoints() -command! -nargs=1 VBGeval call vebugger#std#eval() -command! -nargs=0 VBGevalWordUnderCursor call vebugger#std#eval(expand('')) -command! -nargs=1 VBGexecute call vebugger#std#execute() +command! -nargs=1 VBGeval call vebugger#userAction('std_eval', ) +command! -nargs=0 VBGevalWordUnderCursor call vebugger#userAction('std_eval', expand('')) +command! -nargs=1 VBGexecute call vebugger#userAction('std_execute', ) -command! -range -nargs=0 VBGevalSelectedText call vebugger#std#eval(vebugger#util#get_visual_selection()) -command! -range -nargs=0 VBGexecuteSelectedText call vebugger#std#execute(vebugger#util#get_visual_selection()) -command! -range -nargs=0 VBGrawWriteSelectedText call vebugger#writeLine(vebugger#util#get_visual_selection()) +command! -range -nargs=0 VBGevalSelectedText call vebugger#userAction('std_eval', vebugger#util#get_visual_selection()) +command! -range -nargs=0 VBGexecuteSelectedText call vebugger#userAction('std_execute', vebugger#util#get_visual_selection()) +command! -range -nargs=0 VBGrawWriteSelectedText call vebugger#userAction('writeLine', vebugger#util#get_visual_selection()) command! -nargs=+ -complete=file VBGstartGDB call vebugger#gdb#start([][0],{'args':[][1:]}) -command! -nargs=1 -complete=file VBGattachGDB call vebugger#gdb#searchAndAttach() +function! s:attachGDB(...) + if 1 == a:0 + let l:processId=vebugger#util#selectProcessOfFile(a:1) + if 0 < l:processId + call vebugger#gdb#start(a:1, {'pid': l:processId}) + endif + elseif 2 == a:0 + if a:2 =~ '\v^\d+$' + call vebugger#gdb#start(a:1,{'pid': str2nr(a:2)}) + else + call vebugger#gdb#start(a:1, {'con': a:2}) + endif + else + throw "Can't call VBGattachGDB with ".a:0." arguments" + 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 VBGstartNInspect call vebugger#ninspect#start([][0],{'args':[][1:]}) command! -nargs=+ -complete=file VBGstartPDB call vebugger#pdb#start([][0],{'args':[][1:]}) @@ -39,7 +75,7 @@ if exists('g:vebugger_leader') \'c':'VBGcontinue', \'t':'VBGtoggleTerminalBuffer', \'b':'VBGtoggleBreakpointThisLine', - \'B':'VBGclearBreakpints', + \'B':'VBGclearBreakpoints', \'e':'VBGevalWordUnderCursor', \'E':'exe "VBGeval ".input("VBG-Eval> ")', \'x':'exe "VBGexecute ".getline(".")',