This file documents the STK debugger STKDB.

This is the First Edition, July 2003, of Debugging with STKDB: the STK Source-Level Debugger

Copyright (C) 2003 Paul N. Hilfinger

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with the Invariant Sections being ``Free Software'' and ``Free Software Needs Free Documentation'', with the Front-Cover Texts being ``A GNU Manual,'' and with the Back-Cover Texts as in (a) below.

(a) The Free Software Foundation's Back-Cover Text is: ``You have freedom to copy and modify this GNU Manual, like GNU software.  Copies published by the Free Software Foundation raise funds for GNU development.''

Debugging with STKDB
********************

This file describes STKDB, the STK symbolic debugger.

This is the First Edition, July 2003, for STKDB Version 1.0.

Copyright (C) 2003 Paul N. Hilfinger

Summary of STKDB
****************

Generally speaking, a ``debugger'' is a program that allows its users to run, control, and observe other programs in action for the purpose of diagnosing problems in these programs.  STKDB does this for Scheme programs written for the STK system. It is deliberately designed to resemble the GNU debugger, GDB, which provides support for many languages, including Ada, C, C++, Java, Fortran, and Modula~2.

STKDB provides a number of features that are common to most modern debuggers for other languages as well. You can:

  * Add (invisible) instrumentation to functions you have written so that you can track them.

  * Place breakpoints on expressions of your program, which cause STKDB to stop the program when it evaluates those expressions.

  * Step a program, evaluating one subexpression at a time and stopping after each one.

  * Find out where a program has stopped, whether due to a breakpoint, an error, or an interrupt (i.e., a C-c).

  * Find out how the program arrived at a point where it has stopped---that is, to find out what function call started the execution of the function body where the program stopped, and in turn how that function call came to be evaluatued.

  * Examine the variables of a stopped program---in fact, you can evaluate any Scheme expression at the point where the program stopped.

STKDB is itself a Scheme program---a collection of function and variable definitions---that runs on top of STK. It is loaded into STK like any other Scheme program.  Once loaded, it provides a function, stkdb:debug-file, which instruments and loads other files full of Scheme definitions.  Ordinarily, the functions loaded in this way behave like ordinary Scheme functions.  However, STKDB also provides a function, stkdb, that starts a special read-evaluate-print loop (in other words, it acts as STK does normally, reading what you type, evaluating it, and printing the result).  This loop understands commands for performing special debugging functions, such as setting breakpoints, as well as the usual task of evaluating expressions.

While you can use this simple command-line interface to talk to STKDB, you will probably find it much more convenient to use Emacs, which has special Scheme debugging support that is integrated into the Scheme execution support already provided. Emacs provides

  * Menu-based access to most STKDB commands.

  * Fast key bindings (``accelerators'') for frequent operations.

  * Simplified setting of breakpoints.

  * Text highlighting to indicate points in your program where execution has stopped.

  * And, of course, access to this documentation.

In the discussion that follows, we will show both the text-based and Emacs-based commands.

Beginning a STKDB Session
*************************

STKDB uses the slib Scheme library; your STK system should first be configured to ``know'' the location of the slib library.  STKDB can then load the library, if necessary. Once STK is properly configured, you can load STKDB into your STK session with the commands

     (load "DIR/stkdb.scm")
     (import stk-debugger)

where DIR is the directory in which stkdb.scm is installed.

This is all much simpler in Emacs.  Assuming you have properly installed the STKDB Elisp file, stkdb.el, you need only issue the command M-x load-file RET DIR/stkdb.el RET, or if DIR is on your Emacs load-path, then M-x load-library RET stkdb RET will also work.  Once you've done this, Emacs will set up a buffer running Scheme and with STKDB properly loaded as soon as you use the ``Debug File'' menu command (or C-c d) in a Scheme source buffer (see Preparing Files).

Normally, you won't even bother to issue these load commands by hand, but will instead include the line

     (load "stkdb")

in your .emacs file.

Preparing Files for Debugging
*****************************

In order to control the evaluation of a set of functions you want to examine, STKDB needs to instrument them---that is, to add Scheme code to these functions before they are loaded.  The instrumentation is normally invisible to you (there are STK functions that allow you to examine the internal definition of a function, but you'll usually just go back to the source file for that information instead, and STKDB doesn't need to modify any source files).

 -- Function: stkdb:debug-file file

     stkdb:debug-file instruments and loads the Scheme source file FILE.  The FILE argument is a string. Only functions that have been loaded in this fashion can be controlled by STKDB.

          (stkdb:debug-file "problem1.scm")

The easy way to issue this command is from Emacs.  There are several ways:

  * C-c d while visiting a file containing Scheme code will create a Scheme buffer and start the Scheme process, if necessary, and then instrument and load the source file.  This command also starts STKDB; you will see STKDB's prompt in the Scheme buffer.

  * The ``Debug File'' item in the Debugging menu that appears when visiting a buffer of Scheme code does the same thing as C-c d.

Activating the Debugger
***********************

Once STKDB is loaded into a Scheme session running under Emacs (see Setting Up) and you have loaded one or more files containing Scheme functions you want to debug (see Preparing Files), the next step is to activate the debugger so that it controls the evaluation of test cases you type.  The Scheme command (stkdb) puts your Scheme session into debugging mode; you tell that this mode is active because your prompt will either end in [-] or [1], rather than the usual style of prompt (which ends in >).

In debugging mode, an assortment of commands for manipulating and examining the evaluation of your program become available. These are described in the sections that follow.  In addition to these commands, ordinary expression evaluation is available, as at the usual STK prompt.  Typically, you will enter debugging mode, type in a Scheme expression that exercises some piece of program text you are trying to debug, possibly after first setting up some breakpoints in that text.  When something ``interesting'' happens, you will again get a debugging prompt (usually ending in [1], which indicates that you are stopped in the middle of an expression), and can then enter debugging commands.  The command reset (no parentheses needed) will exit from debugging mode, cancelling evaluation of any expression you might be in the middle of.

Stopping and Continuing
***********************

When in debugging mode, evaluation of an expression can be suspended and control given back to you (indicated by a [1] prompt, generally) under several circumstances:

  * The program evaluates an expression that has a breakpoint set on it.

  * You ask STKDB to execute a single evaluation step, and that step completes.

  * Scheme detects a run-time error in your program (e.g., performing a car on the value nil).

Showing Where You've Stopped
============================

Whenever STKDB stops your program during evaluation, it indicates where the stop has occurred.  If you are not using Emacs, the information consists of a file name and line number, which is a bit clumsy to use. When run under Emacs, however, things are much clearer.  Emacs will show you a buffer containing file in which evaluation has stopped, highlighting the precise expression at which you are stopped.  This highlighting is color-coded to indicate the reason for the stop:

  * Green highlighting indicates an expression that is about to be evaluated. It results from breakpoints (see Breakpoints), steps (see Stepping), and interrupts (C-c C-c).

  * Light blue highlighting indicates an expression that has just finished being evaluated.  The value just returned from that expression may be displayed in the Scheme execution buffer, depending on how you got there. This kind of stop can result from stepping or continuing a program with the finish command (see Continuing).

  * Purple highlighting indicates an expression that is in the middle of being evaluated.  This is how the up command (see Viewing Callers) indicates where a certain function was called.

  * Red highlighting indicates an expression whose evaluation caused an error.

Setting and Clearing Breakpoints
================================

A breakpoint is a point in program text at which to break off evaluation, returning control to the programmer.  What makes them interesting is that, unlike errors, it is then possible to continue evaluation from the breakpoint.

In debugging mode (the [-] or [1] prompt), the command

     break FUNC

sets a breakpoint at the beginning of the code for function FUNC (this is a command, not a Scheme function call; there are no parentheses). You can only set breakpoints in functions that have been instrumented first (see Preparing Files). You can use br or just b as shorthand for break.  The command

     break FILE:LINE

sets a breakpoint at line number LINE (numbering from 1) of file FILE.

As usual, it is much more convenient to use Emacs for setting breakpoints. When the cursor is positioned at the line on which you want to break in a Scheme source buffer, the Emacs command C-x SPC, or the ``Set Breakpoint'' menu command in the Debugging menu, will set a breakpoint on the indicated line.

In response to these commands, STKDB will confirm with a breakpoint number, which you can use subsequently to refer to the breakpoint.  For example,

     stk[-] br sample1.scm:200
     Breakpoint 1 at sample1.scm:200
     stk[-] break replace-all
     Breakpoint 2 at sample1.scm:20

To remove breakpoints, use the command delete in debugging mode. This command allows you to remove either specific breakpoints (by the numbers assigned by the break command) or to remove all breakpoints:

     stk[-] delete 1 2
     Removing breakpoint 1
     Removing breakpoint 2
     stk[-] delete
     Remove all breakpoints? [yn] y
     Removing all breakpoints.

In Emacs, put the cursor on the line whose breakpoint you wish to remove and use the ``Clear Breakpoint'' menu item, or use the ``Clear All Breakpoints'' menu item to delete all breakpoints. In addition, re-loading a file under STKDB (as with C-c d) removes all breakpoints on that file.

To see the current set of breakpoints set in your program, use the command

     info break

in Scheme debugging mode.

Stopping Conditionally
======================

Sometimes, you may have observed that there is a  problem at some point in your program that only occurs under certain infrequent circumstances (such as a list being null).  Placing a breakpoint at that point will force you to issue continue commands annoyingly many times before you get to the problem.  One way around this is to make the breakpoint conditional.

The command

     condition BPNUM EXPR

where BPNUM is the number of an existing breakpoint and EXPR is a Scheme expression, will cause STKDB to stop at the breakpoint only if EXPR evaluates to a true value.  You may abbreviate condition as cond. STKDB evaluates EXPR in the frame of the breakpointed expression (see Viewing Values).  To cancel the condition, use

     condition BPNUM

(that is, without the conditional expression). In Emacs, the ``Condition Breakpoint'' menu item will make the breakpoint at the cursor conditional, prompting for the condition in the minibuffer (simply typing RET in response will deconditionalize the indicated breakpoint).

For example, suppose that a certain function is supposed to return a list of symbols, but sometimes returns a list with a few scattered null lists as elements.  The function constructs this return value using an expression

        (cons (car L) rest)

You place a breakpoint on this line, to which STKDB responds:

     Breakpoint 4 at glorp.scm:44

In order to filter the responses so that you look only at interesting cases, use the command

     cond 4 (null? (car L))

Your program will then stop if L starts with a null list (or if L is not a pair, so that evaluating car causes an error).

Stepping Through One Expression at a Time
=========================================

Stepping a program means evaluating one subexpression at a time (just as a Scheme interpreter would), stopping after each.  This only makes sense when you are in the midst of evaluating an expression in debugging mode and STKDB has stopped, presenting you with a [1] prompt. At that point, you have the following choices:

  * The command step (which may be abbreviated s) continues evaluation until it reaches the next instrumented subexpression (that is, the next Scheme expression in a file that you have loaded with stkdb:debug-file or equivalent key sequence). In Emacs, simply use the F5 key for this purpose.

  * The command next (abbreviated n) continues evaluation until it completes the execution of the current subexpression and reaches the next one after that (or hits another breakpoint). In Emacs, this is the F6 key for this purpose.

To see the distinction between these two, let's suppose that STKDB is stopped at the expression (f (g 3)) below (in Emacs, that expression would be highlighted in green):

     (define (g x) (+ x 3))
     (define (f y) (* y 7))

     (compute (f (g 3)) (h 1))

At this point, a step command moves to (g 3). Another step command moves to (+ x 3) in the definition of g. A third returns us to (g 3), but highlighted in blue (assuming we are using  Emacs) to indicate that we have finished evaluating it.  A fourth step takes us to (* y 7) in the definition of f.  A fifth returns us to (f (g 3)) (in blue), and a sixth takes us to (h 1).

If, on the other hand, we go back to where we started and use a next instead of step, we go from (f (g 3)) immediately to (h 1), skipping the intermediate steps.

When using step, you can arrange to see the value of each subexpression you step through. Use the debugging-mode command show values (the opposite is show novalues), or turn on ``Show All Returned Values'' in the ``Settings'' submenu of the Debugging menu under Emacs.

Tail Recursion and Debugging
============================

In a Scheme program such as this:

     (define (factorial n p)
       (cond ((<= n 1) p)              ;; Breakpoint here
             (else (factorial (- n 1) (* n p))))) ;; Line 3

it looks as evaluation of (factorial 15 1) ought to give backtraces like this:

      [0] foo.scm:2 (factorial)
      [1] foo.scm:3 (factorial)
      [2] foo.scm:3 (factorial)
      [3] foo.scm:3 (factorial)
      ...

But in fact, the actual backtrace will always have one line!  This is because the call to follows on line 4 is tail recursive.  That is, follows returns the value of this recursive call directly, without examining it or performing any other operations with it; the recursive call is the very last action of follows.

The Scheme language actually requires that tail recursions such as this must be able to run indefinitely, just like a loop in other languages. They are not allowed to require increasing amounts of space just to keep track of the call chain.   As a result, you'll normally see ``truncated'' backtraces like this in tail-recursive situations.

You'll also see that the next, step, and finish commands work confusingly when dealing with tail recursion.  For example, if you were evaluating (factorial 5 1) and you are at the expression (<= n 1) at the point that n is 1, any of these commands will simply print 120 (the final answer).  You will not stop again at the call on line~3 (highlighted blue), because all the intervening recursive calls that got you to the point where n is~1 will have been ``forgotten.''

This behavior can be confusing.  If so, STKDB gives you a way to ``cheat.''  In Emacs, simply turn on the the ``Keep Tail Recursion'' flag in the ``Settings'' submenu of the Debugging menu, and then reload the files you're interested in with C-c d, as usual.  You must reload them after changing the value of this flag in order to have an effect. In this mode, STKDB  will treat tail recursions like general recursions.  Of course, your program will now ``blow up'' by exhausting memory in some cases where it wouldn't have before, so you can't expect to be able to do any really long evaluations.

Resuming Normal Execution
=========================

To allow evaluation to proceed to the next breakpoint (or error), use the command continue (abbreviated cont or c).  In Emacs, this is the F8 key.

It is sometimes useful to finish evaluation of the current function, and then stop again, showing the value computed.  The finish command (abbreviated f) does this.  In Emacs, this is the F7 key.  This command leaves us at the call whose body we were just executing (highlighted in blue under Emacs).

For example, suppose we have called (printem foo), where:

     (define (printem LL)
       (if (not (null? LL))
           (begin (print L) (printem (cdr LL)))))

     (define (print L)
       (display "[")
       (let loop ((x L))
         (if (null? x)
             (display "]")
             (begin
               (display (car x))   ;; << STOPPED HERE
               (if (not (null? (cdr x))) (display ","))
               (loop (cdr x))))))

and our program is stopped in print on the indicated line. The finish command will continue the program until we finish printing the current list and return to printem; the expression (print L) will be highlighted in blue.

Examining the Evaluation State
******************************

While your program has stopped, there are basically two sorts of things you'll need to do:

  * Figure you why evaluation got to this particular expression.  In part, this usually means figuring out what function call elsewhere in your program caused evaluation of the function containing this expression.

  * Observe the values of the local variables and parameters at the point in the program where you've stopped.

BackTraces and the Call Chain
=============================

In debugging mode, the command bt (or where or backtrace) prints a backtrace of the current state of evaluation.  If you are running in an Emacs scheme buffer, the menu item ``Backtrace'' displays the output of a backtrace in a separate buffer.

A backtrace is an account of how the current expression (the one you're stopped at) came to be evaluated.  To see what this involves, consider the following program:

     ;; This is file count.scm, line 1
     (define (count-tips tree)
        (if (pair? tree)
            (count-kids-tips (cdr tree)) ;; line 4
            1))
     (define (count-kids-tips kids)
        (if (null? kids) 0
            (+
               (count-tips (car kids))   ;; line 9 << BREAKPOINT HERE
               (count-kids-tips (cdr kids)))))

where we have set a breakpoint at the indicated location, are in the process of evaluating

     (count-tips '(martin
                    (marty (sally tommy matt) (heidi taylor))
                    (donald peter (melinda jessica))
                    (george paul ann (john dana))))

and have stopped a few times at the breakpoint, so that, let's say, we are at the point in the program where kids is (tommy matt). Asking for a backtrace will get us this:

     *[0] /tmp/count.scm:9 (count-kids-tips)
      [1] /tmp/count.scm:4 (count-tips)
      [2] /tmp/count.scm:9 (count-kids-tips)
      [3] /tmp/count.scm:4 (count-tips)
      [4] /tmp/count.scm:9 (count-kids-tips)
      [5] /tmp/count.scm:4 (count-tips)

Translation: we are now at line 9 in count.scm, which is in count-kids-tips; we're there because we were called from line 4, which is in count-tips; we got there by being called from line 9 in count-kids-tips, etc.  If the debugger were perfect, there would be a line [6] that said that we were in count-tips because we got called from the (count-tips '(martin...)) line that the user typed.  Sorry; it's not perfect.

We say that each line in the backtrace denotes a frame, basically an instance of the evaluation of a function. Frame number~0 is known as the innermost frame.  The asterisk marks the current frame, i.e., the one the debugger is examining at the moment.  One can also look at the other frames; see Viewing Callers.  The highlighted expression that Emacs shows you when you stop is the expression within the innermost frame that is currently being evaluated. We'll refer to this point (somewhat archaically) as the program counter of the frame.

In Emacs, a copy of the backtrace gets put in a separate buffer.  You can arrange to have it reproduced automatically each time the program stops at a breakpoint or step by turning on ``Auto-Display Backtrace'' in the ``Settings'' submenu of the Debugging menu.

Looking at Other Frames
=======================

A backtrace (see Backtraces) gives you a rough idea of the overall program state.  For more details, you can examine the stack frames in detail. In debugging mode, the command up will increment the current frame number by one, so that we are looking at what is called the  caller of what used to be the current frame. In Emacs, this is F3 or ``View Caller'' in the Debugging menu. Emacs will highlight the expression at the program counter of this new current frame---typically a function call---in purple,  indicating that it is not the innermost frame. At this point, you can examine the variables in that frame (see Viewing Values).

The inverse operation to up is (of course) down (d), or in Emacs F4 or  ``View Callee'' in the Debugging menu.  In addition, the command frame N (or fr N), immediately makes frame number N the current frame, so that frame 0 returns immediately to the innermost frame.

Changing the current frame has no effect on the commands that step or continue the program.  In effect, we always step or continue from the innermost frame.

Looking at Variables and Parameters
===================================

At any time, the environment associated with the current frame---that is, the values of all variables and parameters that are visible at that point in the program---is also available to the debugger.  In debugging mode, you can print the value of any Scheme expression, EXPR, with the command print EXPR (and you may abbreviate print as pr or p). If the syntax of EXPR does not conflict with that of any debugging command (i.e, if you are not trying to evaluate a simple variable with a name like next, c, etc.), then can you leave off the print, just as you would in an ordinary interactive Scheme session.

The debugging-mode command info locals (Debugging menu item ``See Local Variables'') will print the ``local variables'' of the current frame.  This is basically the set of all parameters and let-bound variables defined with the function associated with the current frame.

By combining printing with the use of the up and down commands (see Viewing Callers), you can look at most of the variables relevant to your program while it is stopped.  Reusing an example from elsewhere, consider:

     ;; This is file count.scm, line 1
     (define (count-tips tree)
        (if (pair? tree)
            (count-kids-tips (cdr tree)) ;; line 4
            1))
     (define (count-kids-tips kids)
        (if (null? kids) 0
            (+
               (count-tips (car kids))   ;; line 9 << BREAKPOINT HERE
               (count-kids-tips (cdr kids)))))

where we are evaluating

     (count-tips '(martin
                    (marty (sally tommy matt) (heidi taylor))
                    (donald peter (melinda jessica))
                    (george paul ann (john dana))))

and are stopped at the indicated breakpoint with the following backtrace:

     *[0] /tmp/count.scm:9 (count-kids-tips)
      [1] /tmp/count.scm:4 (count-tips)
      [2] /tmp/count.scm:9 (count-kids-tips)
      [3] /tmp/count.scm:4 (count-tips)
      [4] /tmp/count.scm:9 (count-kids-tips)
      [5] /tmp/count.scm:4 (count-tips)

At this point, we might type any of the following:

     stk[1] print kids
     (tommy matt)
     stk[1] p kids
     (tommy matt)
     stk[1] kids
     (tommy matt)
     stk[1] pr (car kids)
     tommy
     stk[1] (car kids)
     tommy
     stk[1] info locals
       kids: (tommy matt)

Now suppose we go up to previous frames:

     stk[1] up
     stk[1] info locals
       tree: (sally tommy matt)
     stk[1] (cadr tree)
     tommy
     stk[1] up
     stk[1] info locals
       kids: ((sally tommy matt) (heidi taylor))
     stk[1] (cadr kids)
     (heidi taylor)

In Emacs mode, if there is a backtrace displayed, it will reflect the results of the info locals commands, like this:

      [0] /tmp/count.scm:9 (count-kids-tips)
         kids: (tommy matt)
      [1] /tmp/count.scm:4 (count-tips)
         tree: (sally tommy matt)
     *[2] /tmp/count.scm:9 (count-kids-tips)
         kids: ((sally tommy matt) (heidi taylor))
      [3] /tmp/count.scm:4 (count-tips)
      [4] /tmp/count.scm:9 (count-kids-tips)
      [5] /tmp/count.scm:4 (count-tips)

In addition, you can arrange to have local variables automatically displayed either for the innermost (top) frame or for all frames whenever your program stops.  To see just those for the topmost frame, choose the ``Auto-Display Backtrace/Top Frame'' item in the ``Settings'' submenu of the Debugging menu; to see locals in all frames, choose the ``Auto-Display Backtrace/Locals'' item.

     [file gpl.texi not included]

     [file fdl.texi not included]