493 lines
21 KiB
Plaintext
493 lines
21 KiB
Plaintext
*vebugger.txt*
|
|
|
|
|
|
Author: Idan Arye <https://github.com/idanarye/>
|
|
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...
|
|
* 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
|
|
* NInspect - Node inspect is a node js 8+ command line debugger
|
|
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='<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_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.
|
|
|
|
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:
|
|
|
|
*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
|
|
|
|
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
|
|
* "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
|
|
<
|
|
The *VBGstartGDBForD* command is the same as VBGstartGDB but for Dlang
|
|
programs.
|
|
|
|
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
|
|
* "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".
|
|
|
|
|
|
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.
|
|
|
|
LAUNCHING NINSPECT *vebugger-ninspect*
|
|
|
|
NInspect is launched with *vebugger#ninspect#start*
|
|
>
|
|
call vebugger#ninspect#start('test.js',{'args':['hello','world']})
|
|
<
|
|
The supported extra arguments are:
|
|
* "args": Command line arguments for the debugged script
|
|
* "version": The version of the debugger to run
|
|
|
|
NInspect can also be launched with the *VBGstartRDebug* command:
|
|
>
|
|
VBGstartNInspect script.js hello world
|
|
<
|
|
|
|
|
|
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.
|
|
*:VBGclearBreakpints* 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 <cword> 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 |:VBGclearBreakpints|
|
|
|
|
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.
|