TutorialClispDebugger
Small Clisp Debugger Tutorial

Debugging is implementation dependant.

With clisp, debugging can only be done on interpreted functions. Too little information remains in compiled functions to do any useful debugging.

So, let's take care not to compile our new functions:

[221]> (defun fact (x) (if (< x 1) (/ 1 x) (* x (fact (1- x)))))
FACT
[222]> (defun pf (n) (loop :for i :from n :downto 1 :do (print `(,i ,(fact i)))))
PF
[223]> (pf 4)

*** - division by zero
The following restarts are available:
ABORT          :R1      ABORT
Break 1 [224]> 

The first thing to do, is to dump the stack backtrace. Often, this is all you'll have to do to identify and correct the bug, because clisp displays a lot of information about non compiled functions:

Break 1 [224]> :bt
<1> #< SYSTEM-FUNCTION EXT:SHOW-STACK > 3            ; These frames correspond
<2> #< COMPILED-FUNCTION SYSTEM::PRINT-BACKTRACE >   ; to compiled functions
<3> #< COMPILED-FUNCTION SYSTEM::DEBUG-BACKTRACE >   ; belonging to the 
<4> #< SYSTEM-FUNCTION SYSTEM::READ-EVAL-PRINT > 2   ; debugged itself.
<5> #< COMPILED-FUNCTION SYSTEM::BREAK-LOOP-2-2 >    ; Notice how little
<6> #< SYSTEM-FUNCTION SYSTEM::SAME-ENV-AS > 2       ; information we have:
<7> #< COMPILED-FUNCTION SYSTEM::BREAK-LOOP-2 >      ; only the function name.
<8> #< SYSTEM-FUNCTION SYSTEM::DRIVER >              ; 
<9> #< COMPILED-FUNCTION SYSTEM::BREAK-LOOP >        ;
<10> #< SYSTEM-FUNCTION INVOKE-DEBUGGER > 1          ;
<11> #< SYSTEM-FUNCTION / > 2    ; the divide function call
                               ;  who raised the error.
<12> #< SPECIAL-OPERATOR IF >
                               ; And here we have the form from our code
                               ; who called / with a zero divisor.
                               ; Happily, it's the only division in the form
                               ; so we can identify it immediately.
EVAL frame for form (IF (< X 1) (/ 1 X) (* X (FACT (1- X))))
APPLY frame for call (FACT '0)
<13> 
#<F UNCTION FACT (X) (DECLARE (SYSTEM::IN-DEFUN FACT))
  (BLOCK FACT (IF (< X 1) (/ 1 X) (* X (FACT (1- X))))) > 1
EVAL frame for form (FACT (1- X))
EVAL frame for form (* X (FACT (1- X)))
<14> #< SPECIAL-OPERATOR IF >
EVAL frame for form (IF (< X 1) (/ 1 X) (* X (FACT (1- X))))
APPLY frame for call (FACT '1)
[...]
EVAL frame for form 
(LET ((I N))
 (LET NIL
  (TAGBODY SYSTEM::BEGIN-LOOP (WHEN (< I 1) (GO SYSTEM::END-LOOP))
   (PRINT (LIST I (FACT I))) (PSETQ I (- I 1)) (GO SYSTEM::BEGIN-LOOP)
   SYSTEM::END-LOOP)))
APPLY frame for call (PF '4)
<25> 
#<F UNCTION PF (N) (DECLARE (SYSTEM::IN-DEFUN PF))
  (BLOCK PF (LOOP :FOR I :FROM N :DOWNTO 1 :DO (PRINT `(,I ,(FACT I))))) > 1
EVAL frame for form (PF 4)
Printed 25 frames
Break 1 [224]> 

But let's assume that there was another division in the other branch of the IF. How could we know what branch was being executed? We could re-evaluate the condition. First move up the frames to the EVAL frame, were we'll have access to the lexical variables. Only one :u command is needed because it skips over the compiled frames where we cannot get at the lexical variables (they might be optimized out!).

Break 1 [224]> :u
<1> #< SPECIAL-OPERATOR IF >
EVAL frame for form (IF (< X 1) (/ 1 X) (* X (FACT (1- X))))

Then we can enter any lisp form, like the condition:

Break 1 [224]> (< X 1)
T
and see that it's the "then" branch that was taken, and therefore it's (/ 1 X) which raises the error. Let's check that X is

Break 1 [224]> x
0

Indeed.

Now, we have the choice of aborting the debuging session and going back to the source file to correct the bug, reload the file, or just reevaluate the defun, and run it again, or try to correct the bug inside the debugger, and proceed from there.

In the first case, we can use :q to go back to the toplevel.

In the second case, let's redefine fact:

Break 1 [224]> (defun fact (x) (if (< x 1) 1 (* x (fact (1- x)))))
FACT

then we return from the failed EVAL frame, giving the result it should return:

Break 1 [224]> :rt
Values: 1

(4 24) 
(3 6) 
(2 2) 
(1 1) 
NIL
[225]> 

And we see:

  1. how the current call (fact 4) proceeds to completion, and
  2. how the next calls (fact 3) ... (fact 1) invoke the new function.

Note that if the failed EVAL frame had been in a loop inside the function, the loop would still call iteratively the wrong version. The new version of the function is only taken into account for the new function calls. To proceed in such a case, we would have to move up the frames (:u) until we could return (:rt) from the whole function.

Use also the :help command to get the list of debugger commands, and see: CLISP Implementation Notes - Debugger Chapter


Categories: Online Tutorial CLISP Debugging Keywords: debug debugger debugging tutorial clisp