Debugging

Complex programs usually start as scribbling in a buffer, evaluated in the REPL. The scribbling then becomes a module, which later becomes a library, and eventually the program takes its more or less final shape. In the end, it is compiled as a fully static binary and shipped to a production server.

One thing is certain: in all stages of a program's evolution there will be bugs. Gerbil offers various mechanisms for debugging programs, from inception to production.

See also Testing.

Debugging in the REPL

For interactive debugging, you can just load the relevant dynamic modules in the gxi REPL and utilize its facilities. If the module is loaded from source, then you will also get rudimentary stepping support.

Here are the debugging commands avaiable in the gxi REPL:

$ gxi
Gerbil 0.17.0-336-g94ff9728 on Gambit v4.9.5-40-g24201248
> ,help
Gambit v4.9.5-40-g24201248
,?   | ,help    : Summary of comma commands
,h   | ,(h X)   : Help on procedure of last error or procedure/macro named X
,q              : Terminate the process
,qt             : Terminate the current thread
,t              : Jump to toplevel REPL
,d              : Jump to enclosing REPL
,c   | ,(c X)   : Continue the computation with stepping off
,s   | ,(s X)   : Continue the computation with stepping on (step)
,l   | ,(l X)   : Continue the computation with stepping on (leap)
,N              : Move to specific continuation frame (N>=0)
,N+  | ,N-      : Move forward/backward by N continuation frames (N>=0)
,+   | ,-       : Like ,1+ and ,1-
,++  | ,--      : Like ,N+ and ,N- with N = nb. of frames at head of backtrace
,y              : Display one-line summary of current frame
,i              : Display procedure attached to current frame
,b   | ,(b X)   : Display backtrace of current continuation or X (cont/thread)
,be  | ,(be X)  : Like ,b and ,(b X) but also display environment
,bed | ,(bed X) : Like ,be and ,(be X) but also display dynamic environment
,e   | ,(e X)   : Display environment of current frame or X (proc/cont/thread)
,ed  | ,(ed X)  : Like ,e and ,(e X) but also display dynamic environment
,st  | ,(st X)  : Display current thread group, or X (thread/thread group)
,(v X)          : Start a REPL visiting X (proc/cont/thread)

See also the Interactive Gerbil Shell page for some utilities useful for debugging in the REPL.

Debugging with Exception Stack Traces

Gerbil goes to a lot of effort to ensure all exceptions have full stack traces and a proper error context pointing to the location of the source code where the fault condition occured.

If you have an exception stack trace, you can navigate to the offending code in GERBIL_PREFIX/src and understand what the problem is. Once you understand it, you can dynamically load the module where the fault originated in the gxi REPL to reproduce and fix the issue. Once the issue is fixed, you should probably add a regression test as well.

Debugging Hard Crashes

Occasionally, if you are using a (not safe) declaration in some performance critical piece of code, there can be segfaults. All the standard Gerbil modules included in libgerbil have debug symbols enabled, so you can just run the program in gdb and get the location of the crash.

If the crash is inside libgerbil, then you will most like have exact location pointing to the expanded source of the crash. You can find the offending code in GERBIL_PREFIX/lib/static. Note that we could have the tracked scheme source point to the original, unexpanded source, but we prefer to work with expanded source as we'd like to see the actual code that was compiled and also have the ability to debug potential compiler bugs (these are exceedingly rare, but they can happen).

If the crash is inside your own library or main code and you have an input repro, you can just load the executable module of your program dynamically with gerbil :your-executable-module input-param .... So within gdb, you can gdb gerbil and r :your-executable-module input-param. This will get you source location, as all dynamically executable modules are compiled with debug symbols.

If the crash is non deterministic, you can build your executable with gerbil build --debug. This will get you debug symbols for the binary and you can just run it directly with gdb until the crash occurs. The actual expanded source files will be in your build's GERBIL_PATH/lib/static. So if you built your project with gerbil build --debug, these will be located in .gerbil/lib/static.

Note

Sometimes you might get a crash that has no usable stack trace. This can occur if you are calling a non procedure in unsafe context or because of some very low level bugs. If that happens, you will need to guess a bit where the crash is coming from and disable your (not safe) declarations in the relevant module so that you get an exception with a stack trace instead.

If you determine that such a crash occurs because of some low level Gerbil or Gambit bug, please file an Issue and/or get in touch with the Gerbil development team on gitter.

Debugging Live Programs

You can use a remote/network REPL to connect to a running program and evaluate code that helps you inspect and modify the state of the program.

Gerbil offers a couple of different options for this:

  • The embedded Network REPL is useful for any running program in your development workstation or laptop. You can just use telnet to connect, but you have to manually start the repl server inside your program.
  • Servers in an Actor Ensemble automatically have network repl capabilities for debugging and loading code. You can interact with them using the gxensemble tool.

Once you have a REPL to a running program, you can interact with it by evaluating code. Gerbil also provides some useful debugging libraries you can use to inspect the state of the program, debug memory usage and leaks, and so on.