Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
DEBUGGING WITH SCM
by
Gary T. Leavens
Department of Computer Science
File $Date: 2000/08/17 21:28:04 $
This document describes how to use the debugging facilities of the SCM
interpreter.
Aside from trace, I seldom use more advanced debugging facilities.
I find it more helpful to hand-simulate my program, or to think about
what it is doing instead of using the Portable Scheme Debugger (PSD).
When necessary, I usually add some displayln statements
(see section 2.5 of "Scheme and the Art of Programming", which is on reserve).
You can do this too, as it saves you from the trouble of learning the
PSD system. Besides avoiding complications, you may note that
the technique of adding print statements is language independent
(it will work for C++, Pascal, etc. too).
However, as always, more powerful tools are often useful,
and using "trace" (see 1.2 below) is faster than adding print statements.
1. WHAT HAPPENED?
Consider the following (buggy) Scheme procedure.
(define member?
(lambda (item ls)
(or (member? item (cdr ls))
(equal? (car ls) item))))
Let's try it out.
> (member? 3 '(1 2 3))
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
Here's the basic thing an interpreter should do for you, give you
information about what happened. First, of course, look at the error
message. Note first of all, that it says that the procedure "cdr" was
called with a type of argument it didn't expect. The value of the
argument is (). This is the same thing that happens when you type the
following.
> (cdr '())
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
Looking at the error message from the expression (member? 3 '(1 2 3))
thus may be enough to tell you what happened. At some point to fix the bug you
will have to use this information to deduce how the error could have occurred.
But you may need more information.
1.1 THE STACK BACKTRACE
The current SCM interpreter (when built with the CAUTIOUS feature)
automatically prints a stack backtrace for you when the interpreter
encounters an error. For example, what you actually see in the first
example above is the following.
> (member? 3 '(1 2 3))
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
+; (#@cdr #@0+1)
0; ((#@or (#@member? #@0+0 (#@cdr #@0+1)) (equal? (car ls) item)) ...
4; (#@member? 3 (#@quote (1 2 3)))
This stack trace has the current expression being evaluated at the top
(with the plus sign, +, next to it). This is showing us that it is
trying to take the cdr of something, which in this case has the empty
list, (), as its value. The name #@cdr is an internal name for the
built-in procedure cdr. The argument, #@0+1, is an internal lexical
address) form of the argument. In this case it refers to the formal
parameter "ls" in the definition of "member?". (It's at lexical depth
0, meaning it's in the same procedure, and is the formal parameter
numbered 1, counting the list starting from 0.) The current stack
frame, with the 0 at the left, is the body of "member?". You can tell
that it is evaluating the "or" special form, and has not yet started
on the second argument to "or", because (equal? (car ls item)) has not
yet been converted to an internal form (with the #@ stuff). This
stack frame is trying to get the value of "(cdr ls)".
The "4" to the left of the oldest stack frame,
which is our call to "member?", would normally mean that it is 4
levels down in the stack. In this case, because of Scheme's tail
recursion optimization, stack frames have been reused, which is why
there are not 3 other stack frames visible.
If you look at the code, this kind of information should tell you
where the error is occuring, and a bit about what has happened so far.
This may be enough to localize the error.
Exercise: Looking back at the definition of "member?",
can you explain what caused the error now?
But you still may want more information about what arguments
are being passed...
1.1 TRACING
One help, which saves you time in putting in print statements,
is the SCM trace procedure. (For more information on trace, see
http://swissnet.ai.mit.edu/~jaffer/scm_3.html#SEC30.)
This is automatically available for those using the ui54 interpreter.
(If not, type (require 'trace) first.) Let's try it.
> (trace member?)
#
> (member? 3 '(1 2 3))
"CALLED" member? 3 (1 2 3)
"CALLED" member? 3 (2 3)
"CALLED" member? 3 (3)
"CALLED" member? 3 ()
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
The first call, (trace member?) told Scheme to trace all calls
to the procedure member?, which was the argument of trace. Next we
called (member? 3 '(1 2 3)), which starts running, and shows us the trace.
The tracing first shows us the call again:
(member? 3 (1 2 3))
but note that the list (1 2 3) is not quoted. What you are seeing is
the value of the second argument to member?, which is (1 2 3). To be clearer,
consider the following.
> (define test-list '(1 2 3))
#
> test-list
(1 2 3)
> (member? 3 test-list)
"CALLED" member? 3 (1 2 3)
"CALLED" member? 3 (2 3)
"CALLED" member? 3 (3)
"CALLED" member? 3 ()
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
See how the value of test-list is shown in the first line of the trace?
"CALLED" member? 3 (1 2 3)
Now let's look at the next line.
"CALLED" member? 3 (2 3)
It too shows a call to member, this time a recursive call.
The nesting level is shown by the indentation. This call has a smaller list
for its second argument, the list (2 3). Similarly, the next call has (3)
as its second argument. And the last call before the error has ()
as its second argument.
Exercise: Looking back at the definition of "member?",
can you explain what caused the error now?
We can get still more information by tracing procedures that member? calls.
For example, lets trace cdr.
> (trace cdr)
#
> (member? 3 test-list)
"CALLED" member? 3 (1 2 3)
"CALLED" cdr (1 2 3)
"RETURNED" cdr (2 3)
"CALLED" member? 3 (2 3)
"CALLED" cdr (2 3)
"RETURNED" cdr (3)
"CALLED" member? 3 (3)
"CALLED" cdr (3)
"RETURNED" cdr ()
"CALLED" member? 3 ()
"CALLED" cdr ()
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
It should be pretty clear what's happening now, so let's just explain
a bit more of the trace output. The first 3 lines
"CALLED" member? 3 (1 2 3)
"CALLED" cdr (1 2 3)
"RETURNED" cdr (2 3)
show the first call to member?, and within member? a call to cdr,
with argument (1 2 3). This call to cdr returns the list (2 3).
The indentation tells you that the call to (cdr (1 2 3))
happened ``inside'' the call of (member? 3 (1 2 3)); that is, after
the call of (member? 3 (1 2 3)) started and before it returned a result.
Nothing interesting happened inside the call of cdr, so we just saw its
result.
If you are seeing too much happening (a common problem in debugging)
you may want to stop tracing certain procedures. This can be done using
untrace. (Note that untrace is calling cdr, and until it's done, we see
the effects of the still existing trace to cdr.)
> (untrace cdr)
"CALLED" cdr (untrace cdr)
...
"RETURNED" cdr #[proc]
#
> (member? 3 test-list)
"CALLED" member? 3 (1 2 3)
"CALLED" member? 3 (2 3)
"CALLED" member? 3 (3)
"CALLED" member? 3 ()
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
You can untrace all traced procedures by calling untrace without any arguments.
> (untrace)
(member?)
> (member? 3 test-list)
ERROR: cdr: Wrong type in arg1 ()
;STACK TRACE
...
Let's summarize trace and untrace
(trace proc-name) turns on tracing of procedure named proc-name
(untrace proc-name) turns off tracing of procedure proc-name
(untrace) turns off all tracing
1.2 DEBUGGING
If you use emacs, there is a debugger you can use.
It's called PSD (for Portable Scheme Debugger).
To find out how to use it, point your web browser at the URL
http://www.cs.tut.fi/staff/pk/scheme/psd/article/article.html