Peter Møller Neergaard
When learning a new programming language, your first step is often to write a ``hello world''-program. The next step you want to take is to figure out how the debugger works: both good and bad programmers make programming errors, but the former knows and prepares for it. This is where the debugger comes in: despite the name it can't find your error, but it can help you locate the place where your code does not work as you expect.
In general a debugger is a simple but rather effective program. It allows you to insert breakpoints in your program. When the program reaches the breakpoint, it will be paused, and you will enter the debugger. Inside the debugger you can: look up or change a variable, run the program step by step or line by line, or simply resume execution until the program reaches another breakpoint (or terminates). You can thus not only pinpoint the place where the programs behaves wrongly, by setting variables you can recover from the error and continue the execution. Together the functions allow you perform a fine surgery on a faulty program while it is running.
For this course, we provide a symbolic debugger, psd. The debugger is written in Scheme and works as follows: it takes your Scheme program, say, foo.scm, and turns it into another Scheme program, say, foo-annotated.scm, with annotations that allow it to be debugged. When used in the Scheme interpreter foo.scm and foo-annotated.scm behave the same except that you can follow the evaluation of the latter step by step or stop it at an arbitrary location. When used inside Emacs (not XEmacs) this can be done behind the scene so it works (almost) as running your Scheme program.
The workings of psd is well-described in the Quick Introcution accompanying psd. This document therefore contains only notes on using psd on the Berry Patch and a walk-through example.
Before you can use psd you need to make sure that it is available in Emacs. This is done through the general Emacs settings file for the class. You can do one of two things:
<ESC> x load-file <RET> ~cs21b/emacs/emacs.el
(load-file (expand-file-name "~cs21b/emacs/emacs.el"))
We will now walk through an example of using the debugger. Though contrived it will illustrate most of the functions of the debugger. We consider the following faulty implementation of Fibonacci (can you spot the error right away?):
(define (fib n)
(if (= n 1)
n
(+ (fib (- n 1)) (fib (- n 2)))))
We now proceed as follows:
. We open a new
buffer and type in the code above (or simply
download it).
1 ]=> ;Loading "emacs/psd/psd-scheme.scm" ;Loading "emacs/psd/qp.scm" - done ;Loading "emacs/psd/version.scm" - done ;Loading "emacs/psd/instrum-scheme.scm" - done ;Loading "emacs/psd/pexpr.scm" - done ;Loading "emacs/psd/read-scheme.scm" ;Loading "emacs/psd/read.scm" - done - done ;Loading "emacs/psd/runtime.scm" - done ;Loading "emacs/psd/primitives-scheme.scm" - done psd version 1.1 - done ;Value: psd-mode-initialized
We switch back to the buffer with the Fibonacci program by
typing C-x 4 b <RET>. The screen should now look something
like the picture to the right.
This moves us to the Scheme buffer and we get a response like
1 ]=> ;Loading "/tmp/psd223936Sxz" - done ;Value: okThe loading reveals the annotation described in the introduction on Page
: our definition has been annotated, stored
in a temporary file, and is loaded from that file.
1 ]=> (fib 5) ;Aborting!: maximum recursion depth exceededbecause our program is faulty. So now it is time to put the debugger to use.
We will insert a breakpoint at the second line of fib. We move the cursor to the line (if (= n 1) and set the breakpoint by typing C-x <SPC>. We see the response in the Scheme buffer:
1 ]=> ;Value 1: "Breakpoint at /home/turtle/teaching/cosi21b/fib.scm:2"
1 ]=> (fib 5) (fib 5) psd>psd> is the prompt of the debugger indicating that it is expecting an input. The line (fib 5) describes the context which is the expression we are about to evaluate.
The position is also indicated in the program buffer by
a
in the left border next to the scroll bar.
This is marked with a circle in the figure to the right.
psd> val n 5
psd> s (= n 1) psd> s (= n 1) ==> () psd> s (+ (fib (- n 1)) (fib (- n 2))) psd> s (fib (- n 2)) psd> s (- n 2) psd> s (- n 2) ==> 3 psd> s (fib 3) psd> s (= n 1) psd> s (= n 1) ==> ()The debugger reveals all the small steps the program goes through: to evaluate (+ (fib (- n 1)) (fib (- n 2))) the subexpression (fib (- n 2)) is evaluated which again requires the evaluation of the subexpression (- n 2) which; the latter subexpression turns out to evaluate to the value 3.
psd> n (+ (fib (- n 1)) (fib (- n 2))) psd> n (fib 1) psd> n n psd> n (fib 1) ==> 1 psd> n (fib 2)At first, this can look confusing, but the system is that the program paused every time the current line changes. Thus when evaluating the expression (+ (fib (- n 1)) (fib (- n 2))), the program needs to evaluate the subexpressions (fib (- n 1)) and (fib (- n 2)). The latter is evaluated first which results in the call of (fib 1); this call leads us to the conditional which occurs on a different line. And from the conditional we go to the evaluation of the consequent n. This brings us back to the original expression where (fib (- n 1)) should be evaluated; this results in a call to (fib 2).
psd> g (fib 0) psd> g (fib -2)This finally reveals our mistake: we have forgot the < in the less-than-or-equal test.
psd> r 0 (fib -1) psd> r 0 (fib 1) psd> g (fib 4) psd> g (fib 2) psd> g (fib 0) psd> r 0 (fib 1) psd> g (fib 3) psd> g (fib 1) psd> g (fib 2) psd> g (fib 0) psd> r 0 (fib 1) psd> g ;Value: 5Notice that we have to specify the correct return value every time the program starts evaluating (fib 0).
(define (fib n)
(if (<= n 1)
n
(+ (fib (- n 1)) (fib (- n 2)))))
1 ]=> (fib 5) ; Value: 5revealing that our breakpoint was cleared when we provided the new definition.
The following is a couple of small exercises that you can do before you throw yourself at debugging your own programs.
Take the two implementations of factorial in Section 1.2.1. Step through the execution to accomplish yourself with the debugger and how Scheme evaluates expressions.
Consider the following faulty implementation of the fast exponentiation.
(define (fast-expt b n)
(cond ((= n 0) 0)
((even? n) (square (fast-expt b (/ n 2))))
(else (* b (fast-expt b (- n 1))))))
Use the debugger to locate and correct the error.