The Lisp debugger
Tracing function calls
Breakpoints
Fast links versus debugging
Whenever code runs into a Lisp error, it displays the error in a "LISP error" message box, with a short description of the problem.
Such errors can be debugged in more detail by enabling the built-in Lisp debugger:
(setf si::*enter-break-handler* t)
With the "break handler" enabled, Lisp errors will activate the Lisp debugger. This debugger is command-line based; it allows to list stack backtraces, inspect local and global variables, manipulate symbols and variables, call your own Lisp code to view data structures et cetera. In fact, you can call any Lisp code from the debugger prompt!
As an example, define the following function:
(defun testfn(x y) (print x) (print foobar) (print y))
Load the function and run it without arguments:
(testfn)
You will now get the usual error message box complaining about the incorrect number of parameters. Click "OK" in the message box. The prompt line now reads
[1]: Enter break command
This indicates that the Lisp break handler is now active. The Lisp error message from the error box is also echoed to standard output, so the console window says something like
Error: LISP error: MEI::TESTFN requires two arguments, but only zero were supplied. Signalled by MEI::TESTFN Broken at MEI::TESTFN. Type :H for Help.
Entering :H in the user input line results in a list of break handler commands which can be used to inspect the state of your program. The most useful of these commands are described in the following.
All debugger output is printed to the console window; all inputs are expected in the user input line.
You can also explicitly call the break handler by entering
(break)
in the user input line or even in your Lisp code. This is useful if you
want to examine the state of your program at a certain point with the help
of the debugger, without having to add lots of explicit print
statements to display the state.
If a Lisp error occurs in compiled Lisp code, the error message issued by the break handler is often a little vague. It can help to load the original Lisp file which contains the function in question, and then to run the test again. With more source information, the Lisp error information which you get from the break handler is often a lot more precise and useful.
You can enter any Lisp code directly at the debugger prompt to execute it. This allows to inspect values of variables by simply entering their name, or to call debug functions which visualize the state of the code or the current model.
In addition, the debugger prompt loop understands special debugging commands:
Some functions accept a parameter; if you specify one, you must use the command in a special list notation. As an example, :go 4 will not work, but (:go 4) takes you to level 4 in the invocation stack.
Sometimes you only want to check if a certain function is called, or
which parameters it receives. This is the domain of the trace
function:
(defun testfn(x) (* 3 x)) (trace testfn)
From now on, all calls to testfn
will be logged to standard
output. The log contains both the arguments for the function call and the
return value. A typical trace log looks like this:
1> (TESTFN 42) <1 (TESTFN 126)
This output indicates that the function TESTFN
was called
with the parameter x=42 and returns with a result of 126
(=3*42).
To stop tracing a function, use untrace
:
(untrace testfn)
(trace) prints to a special Lisp stream called *trace-output*. You can open a file stream and then bind *trace-output* to that stream; all trace output will then go to the file. Just remember to restore the previous state of affairs when you're done.
Usually, it is more convenient to use the Common Lisp standard command dribble to capture all Lisp input and output to a file:
(dribble "c:/temp/logfile.txt")
To turn off "dribbling":
(dribble)
The trace macro can also be used to set breakpoints in Lisp code. Here is a simple example:
(trace (testfn :entry (break)))
Whenever someone calls testfn now, the break handler will be entered, and you can inspect the state of the program at this point. Use :r in the user input line to resume program execution.
The same can be accomplished using the convenience macro set-breakpoint which was introduced with OneSpace Modeling 2006/v14.50:
(set-breakpoint testfn)
To clear the breakpoint, use clear-breakpoint.
You can even set conditional breakpoints:
(trace (testfn :entrycond someconditionalform :entry (break))) ;; using trace (set-breakpoint testfn :if someconditionalform) ;; using set-breakpoint
Fast links are a special caching mechanism to speed up function calls. When calling Lisp functions, their address has to be looked up in a symbol table. This is a rather time-consuming process (at least when compared to a direct function call), and therefore the result of the symbol lookup is stored in buffer memory so that future calls to the same function can directly use the cached symbol lookup result. This mechanism is active only for compiled Lisp functions.
When debugging code, this mechanism can get in your way. For example, you might have discovered a bug in a compiled Lisp function. You fix the problem in the corresponding Lisp file, then re-load that file in order to overload the original definition and to test your new version. In some cases, you will find that the old, compiled version of the changed function is still called - because of the fast links mechanism.
To flush the fast links cache, enter
(use-fast-links nil)
Another benefit of flushing the fast links cache is that backtrace listings in the Lisp debugger will display more useful information after (use-fast-links nil).
To reenable the fast links cache:
(use-fast-links t)
© 2023 Parametric
Technology GmbH (a subsidiary of PTC Inc.), All Rights Reserved |