*vebugger.txt* Author: Idan Arye License: Same terms as Vim itself (see |license|) Version: 1.2.3+ INTRODUCTION *vebugger* Vebugger is yet another debugger frontend plugin for Vim, created because I wasn't happy with the other debugger plugins I found. Vebugger currently supports: * Tracking the currently executed command in the source code * 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) 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 * RDebug - a Ruby command line option for debugging Ruby scripts Other implementations can be added with ease, and I will accept pull requests that add such implementations as long as they use Vim's |license|. Vebugger is built under the following assumptions: * While command line debuggers share enough in common to make the creation of such a framework as Vebugger possible, the differences between them are too great to be expressed with regular expression. To support them all at least some code has to be written. * Unlike IDE users, Vim users tend to understand the tools the operate behind the scenes. While Vebugger automates the common features, it allows you to "open the hood" and interact with the debugger's shell directly so you could utilize the full power of your debugger. * I have no intention to aim for the lowest common denominator. If one debugger has a cool feature I want to support, I'll implement it even if the other debuggers don't have it. Vebugger is developed under Linux. It doesn't work properly under Windows due to lack of PTY support. I have neither plans nor means to support OSX, but I will accept pull requests that add OSX support. The features that don't work under windows are: * RDebug. * Displaying output from the debugged program. REQUIREMENTS *vebugger-requirements* Vebugger requires the vimproc plugin, obtainable from: https://github.com/Shougo/vimproc.vim Notice that vimproc needs to be built - there are instructions in the GitHub page. In order for Vebugger to use a debugger, that debugger must be installed and it's executable must either be in the PATH or set with a global variable (see |vebugger-configuration|). In case of RDebug and PDB, which are used from the Ruby and Python modules, the interpreter("ruby" or "python") is the one that must be installed and in the path. CONFIGURATION *vebugger-configuration* If you want to use the keymaps, you need to choose a leader for them by setting *g:vebugger_leader* in your vimrc. Example: > let g:vebugger_leader='d' < 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_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 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 (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' < 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 have a "start" function that accepts two arguments: The file to launch(EXE file or main script file) and a dictionary of other arguments. There should also be a command for launching the debugger with more ease. It should go without saying that you can't debug an executable that's not debuggable with the debugger you use, and you won't get meaningful results if you try to debug binary/bytecode files that were compiled without debugging information(usually the "-g" flag). LAUNCHING GDB *vebugger-gdb* GDB can be launched with *vebugger#gdb#start* > call vebugger#gdb#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 * "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". GDB can also be launched with the *VBGstartGDB* command: > VBGstartGDB a.out hello world < The *VBGattachGDB* command searches for processes launched from the EXE to 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* > call vebugger#jdb#start('Main',{ \'classpath':'classes', \'srcpath':'src', \'args':['hello','world']}) < 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. You can have multiple srcpaths, separated by ':' * "version": The version of the debugger to run 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 ':' separated string. 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* RDebug is launched with *vebugger#rdebug#start* > call vebugger#rdebug#start('script.rb',{'args':['hello','world']}) < The supported extra arguments are: * "args": Command line arguments for the debugged script * "version": The version of the debugger to run RDebug can also be launched with the *VBGstartRDebug* command: > VBGstartRDebug script.rb hello world < LAUNCHING PDB *vebugger-pdb* PDB is launched with *vebugger#pdb#start* > call vebugger#pdb#start('script.py',{'args':['hello','world']}) < The supported extra arguments are: * "args": Command line arguments for the debugged script * "version": The version of the debugger to run PDB can also be launched with the *VBGstartPDB* command: > VBGstartPDB script.py hello world < There are also commands to specify if you want to use python2 or python3: > VBGstartPDB2 script.py hello world VBGstartPDB3 script.py hello world < LAUNCHING MDBG *vebugger-mdbg* Mdbg is launched with *vebugger#mdbg#start* > call vebugger#mdbg#start('App.exe',{ \'srcpath':'src', \'args':['hello','world']}) < The supported extra arguments are: * "srcpath": Where to look for source files * "noConsole": If non-zero, do not open a console for the debugged program * "args": Command line arguments for the debugged program * "pid": Process id to attach to * "version": The version of the debugger to run If you specify "pid", you can't specify "args" and/or "noConsole". If you don't supply "srcpath", Vebugger will assume you are using the current directory for source files. Mdbg does not have a command for starting it, since you usually want to supply "srcpath". You can also search for a process with *vebugger#mdbg#searchAndAttach* > call vebugger#mdbg#searchAndAttach('App.exe','src') < Here the first argument is the executable and the second is the source files directory. This will display a list of available processes to attach to. Notice that unlike GDB, you need "src" here since Mdbg doesn't display full source file paths. USING THE DEBUGGERS *vebugger-usage* *vebugger-commands* Once you have launched a debugger, you can use the following commands to interact with it: CONTROL THE EXECUTION OF THE PROGRAM *vebugger-execution-control* *:VBGstepOver* Continue the execution, stopping at the next statement. *:VBGstepIn* Same as VBGstepOver but stepps into functions. *:VBGstepOut* Continue the execution until the end of the current function. *:VBGcontinue* Continue the execution. 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. *:VBGclearBreakpoints* Clear all breakpoints. EVALUATE EXPRESSIONS *vebugger-evalutate* *:VBGeval* Evaluate and print the expression supplied as argument. *:VBGevalSelectedText* Evaluate and print the selected text. *:VBGevalWordUnderCursor* Evaluate the under the cursor EXECUTE STATEMENTS *vebugger-execute* *:VBGexecute* Execute the statement supplied as argument. *:VBGexecuteSelectedText* Execute the selected text. TERMINATING THE DEBUGGER *vebugger-terminate* *:VBGkill* Terminates the debugger OPENING THE HOOD *vebugger-open-the-hood* You can open the hood and interact with the running debugger directly using the following commands: *:VBGtoggleTerminalBuffer* Opens a buffer that shows everything printed from the debugger interactive shell. *:VBGrawWrite* Sends a line supplied as argument to the debugger interactive shell. *:VBGrawWriteSelectedText* Sends the selected text to the debugger interactive shell. KEYMAPS *vebugger-keymaps* If you set|g:vebugger_leader| in your vimrc you'll get keymaps for the Vebugger commands. The keymaps all start with the leader you set, following with: i |:VBGstepIn| o |:VBGstepOver| O |:VBGstepOut| c |:VBGcontinue| b |:VBGtoggleBreakpointThisLine| B |:VBGclearBreakpoints| e |:VBGevalWordUnderCursor| in normal mode |:VBGevalSelectedText| in select mode E Prompt for an argument for |:VBGeval| x |:VBGexecute| current line in normal mode. |:VBGexecuteSelectedText| in select mode X Prompt for an argument for |:VBGexecute| t |:VBGtoggleTerminalBuffer| r Select mode only - |:VBGrawWriteSelectedText| R Prompt for an argument for |:VBGrawWrite| ARCHITECTURE *vebugger-architecture* Vebugger architecture is a combination of the Pipeline architecture and the Message Bus architecture. Vebugger reads lines from the debugger's interactive shell and creates a read result object. The read handlers look at each line and populate the read result object. Think handlers look at the read result and decide what to do with it. They can interact with the user by operate Vim directly, or they can create write action object. Write handlers look at those write action objects and write to the debugger's interactive shell's stdin. This architecture allows flexibility and code reuse. Read and Write handlers interact with the debugger's interactive shell so they must be debugger specific, but think handlers use read result and write action objects so they don't care about the specific debugger. If Vebugger does not support some feature for some debugger(either because the debugger doesn't support it or because it's not implemented in Vebugger for this specific debugger) the think handler can stay and no harm will be done. The read result objects won't be created so the think handler simply won't get them, and if the think handler creates a write action object it'll get ignored because there is no write handler that can handle it for this specific debugger. The think handle won't crash a debugger that doesn't support it - it'll simply be ignored. READ RESULT OBJECTS *vebugger-architecture-read-result-objects* Whenever Vebugger reads a line from the debugger's interactive shell, it creates a read result object. The read result object does not contain the raw line - it contains a structure that will be filled with the information parsed from that raw line by the read handlers. The read result object starts with a structure defined in the debugger object's "readResultTemplate" field, which is deep copied to create read result objects. The direct fields of the read result objects are actually namespaces. Vebugger only 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, but this has limited usage as these implementations usually don't define think handlers. READ HANDLERS *vebugger-architecture-read-handlers* Read handlers are responsible for parsing the raw lines from the debugger's interactive shell and populating the read result object with the data they gathered from that line. Each read handler should focus on single type of output(e.g. current executed line, output from the debugged program, variable value) and ignore any line that doesn't have that data. Read handlers are added with the "addReadHandler" method of the debugger object. That method accepts a VimScript function and creates an object with the function as it's "handle" method. Alternatively, that object can be created and passed to the "addReadHandler" method. This architecture means that if a read handler is declared as a |Dictionary-function| it can utilize the handler object's state. A read handler accepts 4 arguments: 1) A string with the name of the pipe the line came from("out" or "err") 2) A string with the line itself 3) The read result object that was created for the line 4) The debugger object The read handler function should fill the read result object with information from that line. It should not make assumptions about other read handlers that parsed that line before or will parse that line after and fill the same read result objects. If a read handler needs to parse multiline output, it should use the handler object's state to store information between calls, and only fill the read result object once it reads the last line. THINK HANDLERS *vebugger-architecture-think-handlers* Think handlers are responsible for operating on the data they get from read handlers. Think handlers can operate Vim to display data to the user, or operate the debugger itself(via write actions). Each think handler should have a single responsibility(e.g. jumping and marking the current executed line, printing data previously requested by the user) and only operate when the read result object have the data it needs. Think handlers are added with the "addThinkHandler" method of the debugger object. That method accepts a VimScript function and creates an object with the function as it's "handle" method. Alternatively, that object can be created and passed to the "addThinkHandler" method. This architecture means that if a think handler is declared as a |Dictionary-function| it can utilize the handler object's state. A think handler accepts 2 arguments: 1) A read result object 2) The debugger object A think handler interacts with Vim directly, but it should only interact with the debugger's interactive shell via write actions. WRITE ACTIONS OBJECTS *vebugger-architecture-write-actions-objects* A write action is an abstract command to the debugger, that will be converted to the specific debugger's syntax in the write handlers. A write action can be created in the think handlers or from the outside. A write actions object can contain multiple write actions. When the object gets executed, write handlers handle all the write actions inside it and the write actions object is replaced by a new one. The write actions object starts with a structure defined in the debugger object's "writeActionsTemplate" field, which is deep copied to create write actions objects. The direct fields of the write actions objects are actually namespaces. Vebugger only defines the "std" namespace, and plugins built on top of Vebugger can define a namespaced named after the plugin to contain their own write actions. A debugger frontend implementation can also define it's own namespace. The fields of these namespaces are the names of the write actions themselves. Each write action can either be a list or a dictionary. This structure is important since it defines the execution of the write handlers. Each write action in the write actions object can be a dictionary or a list, depending on whether multiple actions can be executed in the same time. For example, a flow control action is a dictionary(you can't do step-in and step-out at the same time) but a breakpoint management action is a list(you can set multiple breakpoints in the same time). Write actions are created with the setWriteAction(for dictionary type actions) and addWriteAction(for list type actions) methods of the debugger object. Both methods accept three parameters: a namespace, a name, and the action's value(usually a dictionary). WRITE HANDLERS *vebugger-architecture-write-handlers* Write handlers are responsible for writing commands to the debugger's interactive shell. Unlike think handlers, which get a whole read result object and ignore what they don't need, write handlers have one-to-one relationship with write actions. A write handler will only get the write action it is registered for, will not be executed if that write action is empty, and only one write handler can be registered for each write action(namespace+name) Write handlers are added with the "setWriteAction" method of the debugger object, which accepts 3 arguments: a namespace, a name, and a VimScript function and creates an object with the function as it's "handle" method. Alternatively, that object can be created and passed as the third argument for the "setWriteAction" method. This architecture means that if a write handler is declared as a |Dictionary-function| it can utilize the handler object's state. A write handler accepts 2 arguments: 1) A single write action(usually object or dictionary) 2) The debugger object A write handler uses the "writeLine" method of the debugger object to write lines to the debugger. CLOSE HANDLERS *vebugger-architecture-close-handlers* Close handlers are called once the debugger is closed - either it closes itself or the process is killed. Close handlers can be used to tidy things up. For example there is a standard close handler that unmarks the currently executed line. Close handlers are added with the "addCloseHandler" method of the debugger object. That method accepts a VimScript function and creates an object with the function as it's "handle" method. Alternatively, that object can be created and passed to the "addCloseHandler" method. This architecture means that if a close handler is declared as a |Dictionary-function| it can utilize the handler object's state. This is done for consistency with the other types of handlers. The usefulness of this mechanism for close handlers is limited since they are only called once and have no use for storing state. A close handler accepts a single argument - The debugger object. DEBUGGER STATE *vebugger-architecture-debugger-state* The debugger object, which is passed as an argument to all handlers, has a "state" field that can be used for storing data and configuration. This is mostly useful for think handlers, but other handlers may use it as well(for example, the JDB read handlers use a configuration field with the source path to convert the JDB output(that uses class names instead of file names) to actual paths). The direct fields of the "state" field are actually namespaces. Vebugger 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') <