Next: , Previous: Subproblems and Reductions, Up: Debugging

5.2 The Command-Line Debugger

There are two debuggers available with MIT/GNU Scheme. One of them runs under Edwin, and is described in that section of this document (see Edwin Debugger). The other is command-line oriented, does not require Edwin, and is described here.

The command-line debugger, called debug, is the tool you should use when Scheme signals an error and you want to find out what caused the error. When Scheme signals an error, it records all the information necessary to continue running the Scheme program that caused the error; the debugger provides you with the means to inspect this information. For this reason, the debugger is sometimes called a continuation browser.

Here is the transcript of a typical Scheme session, showing a user evaluating the expression `(fib 10)', Scheme responding with an unbound variable error for the variable fob, and the user starting the debugger:

     1 ]=> (fib 10)
     ;Unbound variable: fob
     ;To continue, call RESTART with an option number:
     ; (RESTART 3) => Specify a value to use instead of fob.
     ; (RESTART 2) => Define fob to a given value.
     ; (RESTART 1) => Return to read-eval-print level 1.
     2 error> (debug)
     There are 6 subproblems on the stack.
     Subproblem level: 0 (this is the lowest subproblem level)
     Expression (from stack):
     Environment created by the procedure: FIB
      applied to: (10)
     The execution history for this subproblem contains 1 reduction.
     You are now in the debugger.  Type q to quit, ? for commands.
     3 debug>

This tells us that the error occurred while trying to evaluate the expression `fob' while running `(fib 10)'. It also tells us this is subproblem level 0, the first of 6 subproblems that are available for us to examine. The expression shown is marked `(from stack)', which tells us that this expression was reconstructed from the interpreter's internal data structures. Another source of information is the execution history, which keeps a record of expressions evaluated by the interpreter. The debugger informs us that the execution history has recorded some information for this subproblem, specifically a description of one reduction.

What follows is a description of the commands available in the debugger. To understand how the debugger works, you need to understand that the debugger has an implicit state that is examined and modified by commands. The state consists of three pieces of information: a subproblem, a reduction, and an environment frame. Each of these parts of the implicit state is said to be selected; thus one refers to the selected subproblem, and so forth. The debugger provides commands that examine the selected state, and allow you to select different states.

Here are the debugger commands. Each of these commands consists of a single letter, which is to be typed by itself at the debugger prompt. It is not necessary to type <RET> after these commands.

Traversing subproblems
The debugger has several commands for traversing the structure of the continuation. It is useful to think of the continuation as a two-dimensional structure: a backbone consisting of subproblems, and associated ribs consisting of reductions. The bottom of the backbone is the most recent point in time; that is where the debugger is positioned when it starts. Each subproblem is numbered, with 0 representing the most recent time point, and ascending integers numbering older time points. The u command moves up to older points in time, and the d command moves down to newer points in time. The g command allows you to select a subproblem by number, and the h command will show you a brief summary of all of the subproblems.
Traversing reductions
If the subproblem description says that `The execution history for this subproblem contains N reductions', then there is a “rib” of reductions for this subproblem. You can see a summary of the reductions for this subproblem using the r command. You can move to the next reduction using the b command; this moves you to the next older reduction. The f command moves in the opposite direction, to newer reductions. If you are at the oldest reduction for a given subproblem and use the b command, you will move to the next older subproblem. Likewise, if you are at the newest reduction and use f, you'll move to the next newer subproblem.
Examining subproblems and reductions
The following commands will show you additional information about the currently selected subproblem or reduction. The t command will reprint the standard description (in case it has scrolled off the screen). The l command will pretty-print (using pp) the subproblem's expression.
Traversing environments
Nearly all subproblems and all reductions have associated environments. Selecting a subproblem or reduction also selects the associated environment. However, environments are structured as a sequence of frames, where each frame corresponds to a block of environment variables, as bound by lambda or let. These frames collectively represent the block structure of a given environment.

Once an environment frame is selected by the debugger, it is possible to select the parent frame of that frame (in other words, the enclosing block) using the p command. You can subsequently return to the original child frame using the s command. The s command works because the p command keeps track of the frames that you step through as you move up the environment hierarchy; the s command just retraces the path of saved frames. Note that selecting a frame using p or s will print the bindings of the newly selected frame.

Examining environments
The following commands allow you to examine the contents of the selected frame. The c command prints the bindings of the current frame. The a command prints the bindings of the current frame and each of its ancestor frames. The e command enters a read-eval-print loop in the selected environment frame; expressions typed at that REPL will be evaluated in the selected environment. To exit the REPL and return to the debugger, evaluate `(abort->previous)' or use restart. The v command prompts for a single expression and evaluates it in the selected environment. The w command invokes the environment inspector (where); quitting the environment inspector returns to the debugger. Finally, the o command pretty-prints the procedure that was called to create the selected environment frame.
Continuing the computation
There are three commands that can be used to restart the computation that you are examining. The first is the k command, which shows the currently active restarts, prompts you to select one, and passes control to the it. It is very similar to evaluating `(restart)'.

The other two commands allow you to invoke internal continuations. This should not be done lightly; invoking an internal continuation can violate assumptions that the programmer made and cause unexpected results. Each of these commands works in the same way: it prompts you for an expression, which is evaluated in the selected environment to produce a value. The appropriate internal continuation is then invoked with that value as its sole argument. The two commands differ only in which internal continuation is to be invoked.

The j command invokes the continuation associated with the selected subproblem. What this means is as follows: when the description of a subproblem is printed, it consists of two parts, and “expression” and a “subproblem being executed”. The latter is usually marked in the former by the specific character sequence `###'. The internal continuation of the subproblem is the code that is waiting for the “subproblem being executed” to return a value. So, in effect, you are telling the program what the “subproblem being executed” will evaluate to, and bypassing further execution of that code.

The z command is slightly different. It instead invokes the continuation that is waiting for the outer “expression” to finish. In other words, it is the same as invoking the j command in the next frame up. So you can think of this as an abbreviation for the u command followed by the j command.

Wizard commands
The m, x, and y commands are for Scheme wizards. They are used to debug the MIT/GNU Scheme implementation. If you want to find out what they do, read the source code.
Miscellaneous commands
The i command will reprint the error message for the error that was in effect immediately before the debugger started. The q command quits the debugger, returning to the caller. And the ? command prints a brief summary of the debugger's commands.