|
Allegro CL version 11.0 |
Allegro CL provides a number of facilities for debugging and correcting Lisp code. Among these facilities are a set of commands to view and manipulate the runtime stack, a tracer, two steppers and an inspector. All are documented in this document except the inspector, which is documented in inspector.html.
Debugger functionality is included by default in development images (those built with either the include-debugger or include-devel-env arguments to build-lisp-image specified true). If you are building a runtime image and want debugging capability, be sure to specify include-debugger true (you cannot specify include-devel-env true when building a runtime image). See build-lisp-image and building-images.html for information on arguments to build-lisp-image. Note that the stepper is not permitted in runtime images.
A feature in Allegro CL is the ability to debug background processes in separate Lisp listeners. See use-background-streams and Debugging background processes for more information. If you are unable to use the facility described there, note the following. When multiprocessing is enabled, the user must be careful to ensure that he or she is debugging the correct process. The user types to the listener process, and debugs the focused process. Initially, they are the same process. The focus is changed only by user action. But if the focus is changed, the processes will not be the same, and it is important that the user keep track of which process is being debugged. Where relevant, the description of a debugging command specifies how it is used with multiprocessing. Users who are not using the multiprocessing facility need not concern themselves with these distinctions.
Note too that debugging compiled code is affected by what optimizations were in force when the code was compiled. These optimizations are discussed in compiling.html. Suffice it to say here that functions compiled for speed over safety may be harder to debug for the following reasons.
Functions may be compiled inline, so it may be hard to figure out where you are, comparing what you expect from looking at the source to what you see on the stack.
The real error may have gone unnoticed and the reported error may occur much later, perhaps in a different function. Thus you may notice unexpected arguments to a function (resulting in an error) but it may be difficult to discover where these arguments came from.
The error messages themselves may be uninformative. This is because compilation by its nature removes information as it distills the code down to the machine level. Such distillation may not leave enough context to know what functionality was being attempted or where it was being attempted in the original source code.
One more point: difficulties in debugging code that was compiled for speed higher than safety can often be mitigated by recompiling the code with some combination of higher safety, higher debug, and lower speed. Allegro CL responds to the (debug 3)
optimization quality by retaining as much information as possible. So rather than information essential to the debug process being thrown away, it is instead gathered as extra info that can annotate the now bare-bones machine code, and that info can be added back into the lisp as needed for particular debugging needs.
You can include much of the debugger (except the stepper) in a runtime application (see runtime.html for details). Further, the document debugger-api.html provides information about the internals of the debugger and using it, it is possible to provide a customized debugging interface. Note that debugger-api.html does not describe things needed in ordinary use of Allegro CL.
Debugger commands are available all the time, so you are never really in or out of the debugger. When an error occurs, you are put into a break loop (as indicated by a number in brackets in the prompt). See top-level.html for complete details, but briefly, the top-level command :pop will pop up one break level and the top-level command :reset will return you to the top-level, out of all break levels, as the following example shows:
1): (car 1)
USER(the car of 1 which is not listp.
Error: Attempt to take condition type: SIMPLE-ERROR]
[1] USER(2): :reset
[3): USER(
Users trying to debug code will often have occasion to look at the stack for a list of recent function calls. Included in the list will be functions with names like: operator and operator_3op where operator is either a numeric function such as *, +, <, etc, or another function with optional arguments like read-char, etc. These functions are used in place of the expected functions (*, +, <, read-char, etc.) for compiler efficiency. They should be interpreted as the functions named by operator. Thus, for example, <_2op should be interpreted < (i.e. the less-than predicate).
The background-streams facilities described in this section only work when Allegro CL has been started with the fi:common-lisp command to Emacs. The Emacs-Lisp interface is fully described in eli.html. If you are not running under Emacs (or if the backdoor connection between Lisp and Emacs has not been established), you will not get the behavior described here. Note that it is not an error to use background streams when not running Lisp under Emacs. The described effect (creating a new listener in another Emacs buffer) will not happen. But if you later establish the connection, background streams will be used. The function use-background-streams is called automatically when Lisp starts up so it is not necessary to call it explicitly. However, you should call it if you do not get the expected behavior.
If an error occurs in a background process while Allegro Composer is not running, another listener buffer is created at once and output from the error is printed in the new listener buffer. To test the effect of background streams, evaluate the following forms.
fmakunbound 'foo)
("bad process" 'foo) (mp:process-run-function
These forms start a background process that will break and cause a new listener to be created. A message will be printed to the new listener saying that a background process has broken due to foo being undefined.
Background streams work by setting the global values of the following streams to the background stream:
*terminal-io*
*debug-io*
*standard-input*
*query-io*
Note that setting the value of those streams has the following consequences:
*debug-io*
is a background stream and if so, it causes a listener to be created.The second effect is less drastic than it sounds. The existing Lisp listener (what you see when Lisp starts up) has already bound all the listed streams so it does not see the global value at all. There may be a problem if you start your own processes (with, e.g., process-run-function), however. You should (and all applications should) set up your own streams. Consider the following two examples. In the first, the function run by process-run-function takes a stream argument and is passed *terminal-io*
. This will work without error. In the second, the function itself contains a reference to *terminal-io*
. It will signal an error if background streams are used.
;;; This will work:
(mp:process-run-function"foo"
lambda (stream)
#'(format stream "This is from process ~a~%"
(
mp:*current-process*))*terminal-io*)
;;; This will fail:
(mp:process-run-function"foo"
lambda ()
#'(format *terminal-io* "This is from process ~a~%"
( mp:*current-process*)))
The variable *initial-terminal-io* holds the original *terminal-io*
stream when Lisp starts up. It may be useful for processes that aren't connected to a usable *terminal-io*
but wish to produce some output, for example for debugging.
The runtime stack is the entity where arguments and variables local to Lisp functions are stored. When a function is called, the calling function evaluates and pushes the arguments to the called function onto the stack. The called function then references the stack when accessing its arguments. It also allocates space for its local variables. A stack frame is the area on the stack where the arguments to one function call and also its local variables reside. If foo calls bar, which in turns calls yaf, then (at least, and typically) three stack frames are active when yaf is entered. The frame for the most recently called function is at the top of the stack and the frame for the earliest called function is at the bottom of the stack. The commands described in the following subsections access and display the stack in a top-to-bottom order, although not all frames are necessarily displayed. After a frame is examined, it normally becomes the current stack frame. Further reference to the stack will, by default, operate on the current stack frame. When a break level is entered, the current frame pointer is typically the first interesting frame.
A frame object is a type of Lisp object. A frame expression is the printed representation of that object. We are somewhat imprecise in this document in distinguishing between frame objects (the internal Lisp object) and frame expressions (what you see printed) because in most cases, the distinction is not important. Where the distinction is important, we say frame object or frame expression.
The :zoom top-level command prints the evaluation stack. It uses the current stack frame as the center of attention, and prints some number of frames on either side of the current frame. The value of the variable *zoom-display* is the total number of frames to display, and an equal number of frames are printed above and below the current stack frame, if possible. The arguments to the :zoom command control the type and quantity of the displayed stack.
After a :zoom or any of its analogs (such as :top or :bottom) the special variable cl:*
contains the lisp expression representing the current frame. That expression is approximately what would be shown as the current frame by the :zoom with :moderate t
and :function nil
as arguments, regardless of the mode in which :zoom itself displays.
Here are some examples of :zoom command calls. We cause an error by trying to evaluate foo
, which has no value.
1): foo
USER(the value of the unbound variable `FOO'.
Error: Attempt to take condition type: UNBOUND-VARIABLE]
[1] USER(2): [
These arguments to :zoom (only one can be specified true and that value controls further :zoom commands until a new value is specified) control the amount of information printed.
Our examples show backtraces from the undefined error (call to undefined function foo) from the end of the previous section.
In :brief
mode, only the function name is displayed for each frame and more than one frame is displayed on a single line. The current frame (EVAL) is displayed on its own line.
1] user(2): :zoom :brief t
[
Evaluation stack:
error <-
eval <-
tpl:top-level-read-eval-print-loop <- tpl:start-interactive-top-level
In :moderate
mode, each frame is on its own line and the function name and arguments appear. This is the initial setting for :zoom.
1] user(3): :zoom :moderate t
[
Evaluation stack:
error #<unbound-variable @ #x10007cfb212>)
(eval foo)
->(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level 0/1 @ #x10000283812>
#<terminal-simple-stream [initial terminal io] fd
#<Function top-level-read-eval-print-loop> ...)
:intermediate
mode is the same as :moderate
with pc (program counter) suspensions, which also appear in :verbose
mode and are described in more detail there.
1] cl-user(23): :zoom :intermediate t
[
Evaluation stack:
error #<unbound-variable @ #x10007cfb212>) suspended at relative address 472
(eval foo) suspended at relative address 107
->(397
(tpl:top-level-read-eval-print-loop) suspended at relative address
(tpl:start-interactive-top-level 0/1 @ #x10000283812>
#<terminal-simple-stream [initial terminal io] fd
#<Function top-level-read-eval-print-loop>683 ...) suspended at relative address
In :verbose
mode, several lines are used per frame and much more information about arguments is provided.
1] user(4): :zoom :verbose t
[
Evaluation stack:
error
call to = #<unbound-variable @ #x10007cfb212>
required arg: excl::datum rest excl::arguments = nil
&function suspended at relative address 472
----------------------------eval
->call to exp = foo
required arg: function suspended at relative address 107
----------------------------
call to tpl:top-level-read-eval-print-loopfunction suspended at relative address 397
----------------------------
call to tpl:start-interactive-top-level*terminal-io* = #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000283812>
required arg: function = #<Function top-level-read-eval-print-loop>
required arg: = nil
required arg: tpl::args = :unsupplied
&key tpl::initial-bindings function suspended at relative address 683
----------------------------
Note that in verbose mode, like in intermediate mode, each frame specifies the location at which the function was suspended:
error
call to = #<unbound-variable @ #x10007cfb212>
required arg: excl::datum rest excl::arguments = nil
&function suspended at relative address 472
The suspension location may be relative or absolute (it is relative in the example just shown). It represents the value of the program counter at the time the function was suspended (usually because it called another function). The suspension location may be the instruction that caused the suspension (a call instruction or an instruction that caused trapping) or it may be the next instruction to be executed when the function is reactivated.
Suspension locations are interpreted as follows:
relative
: a relative address is the distance from the start of the function's codevector. It can be seen by disassembling the function with disassemble with the :absolute
keyword argument unspecified or specified as nil
(see implementation.html for information on disassemble). The leftmost column of the disassembly shows the relative address of each instruction in bytes. Relative addresses are needed for Lisp functions because their codevectors may move so the actual address of a particular function may change after a garbage collection.absolute
: an absolute address is an address that will not change within the current Lisp process execution. Absolute addresses will be given when a frame represents a runtime operation. Examples of runtime operations are calls to apply and funcall, the majority of whose implementations are written in low-level lisp-assembler code.When a runtime operation is encountered in :verbose
mode, a symbol-table is built (if an up-to-date one does not already exist) if possible. This build may take some time and trigger several garbage collections. After the symbol table is built, the address is associated with a name in the table and printed as its offset.
If the symbol table cannot be built, then no interpretation is given to the suspension location, only the absolute address is shown. Here is an example from the verbose backtrace following the attempt to evaluate an unbound variable:
----------------------------0 arguments.
->call to SYS::..CONTEXT-SAVING-RUNTIME-OPERATION with function suspended at address #x6fa1b1d0 (unbound+404)
Note that any non-lisp function can be disassembled (with disassemble) if it is represented in the symbol table by the string associated with its name. Thus
"qcons") (cl:disassemble
will print the disassembly of the runtime operation "qcons". If the name is not in the symbol table, cl:disassemble will complain that the argument is invalid.
Using the same error as above (trying to evaluate foo
, which is unbound), here is the difference between specifying :all t
and :all nil
as arguments to :zoom. The frames hidden by default are those specified by default by :hide.
1] USER(5): :zoom :all nil :moderate t
[
Evaluation stack:
(ERROR UNBOUND-VARIABLE :NAME ...)
->(EVAL FOO)
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
(TPL:START-INTERACTIVE-TOP-LEVEL#x18ad06>
#<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @ #x2df9de> ...)
#<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP @ 1] USER(6): :zoom :all t
[
Evaluation stack:
1 more newer frame ...
...
(ERROR UNBOUND-VARIABLE :NAME ...)
(EXCL::UNBOUND-VARIABLE-HANDLER FOO)5 FOO)
(EXCL::ER-GENERAL-ERROR-HANDLER-ONE
(EXCL::%EVAL FOO)
->(EVAL FOO)
(TPL::READ-EVAL-PRINT-ONE-COMMAND NIL NIL)0)
(EXCL::READ-EVAL-PRINT-LOOP :LEVEL
(TPL::TOP-LEVEL-READ-EVAL-PRINT-LOOP1)
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
... more older frames ...1] USER(7): [
The difference between specifying the :function
argument to :zoom t
or nil
can be shown by one frame, so we have removed all but one displayed frame. With :function t
, the exact function object is identified.
1] USER(7): :zoom :function t
[some lines removed]
Evaluation stack: [note:
#x1216d6> FOO)
->(FUNCALL #<Function EVAL @
1] USER(8): :zoom :function nil
[some lines removed]
Evaluation stack: [note:
->(EVAL FOO)
The :catches
and :specials
modes print information about catches and special-bindings respectively.
Both special bindings and catches are described using an internal Lisp index called a bindstack name pointer, or bnp. A bnp is an index into a binding stack for each thread, and each thread's current bnp is the index into the bindstack of the first empty binding. The bindstack holds both the symbol and its binding value, so it is always incremented and decremented by two and is thus always an even value. Both catches and bindings track bnp values; a binding's bnp value is the index into the bindstack where the global-variable's previous binding was stored, and a catch's bnp value is the index to restore to when a throw finds its way to that catch. The printouts for each are as follows:
special: The name of the special being bound is given, followed by the value labelled bnp which is in fact the index into the pair of values in the bindstack that represent this binding. Since the current value is always in the symbol itself at the time of the binding, the value slot in the bindstack holds what represents the prior value, which is also printed..
catch: The catch tag is printed, which is one of
A symbol representing a symbolic tag.
A <block
address>
designation, where address is the stack address of the catch. This occurs when a block form is named and is thrown to or through.
An <unwind-protect>
designation; a throw will always execute the protected form on its way to execute the tag it was meant to match.
An <integer
value>
designation. Since a catch-tag can be numeric, the value of the tag is shown.
An <unknown>
designation. This situation should not occur.
The catch tag is followed by the last-bnp, a pointer to the most recent variable that has been bound at the time of the catch. It is always 2 less than the value a throw would restore the current-bnp to if it was paired with this particular catch. Finally the relative program-counter value is shown; if the function is disassembled the pc value is the instruction where execution would resume after a throw.
Here we provide a simple function which has some catches in it and some special-bindings. The function does nothing, but is meant only for illustration. Starting by compiling and loading the file, and calling the function:
1): (shell "cat catch.cl")
cl-user(defvar *foo* nil)
(defvar *bar* nil)
(defvar *bas* nil)
(
defun foo (a b)
(let ((*foo* nil))
(catch 'bar
(let ((*bar* t))
(unwind-protect
(catch 'bas
(let ((*bas* 'hi))
(break "baz: ~s ~s" a b)))
(
(bar))))))
defun bar ()
(nil)
0
2): :cf catch
cl-user(;;; Compiling file catch.cl
;;; Writing fasl file /tmp/cfta291721019271
;;; Moving fasl file /tmp/cfta291721019271 to catch.fasl
;;; Fasl write complete
3): :ld catch
cl-user(; Fast loading /net/gemini/home/duane/catch.fasl
4): (foo 10 20)
cl-user(10 20
Break: baz:
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
5): [1c] cl-user(
Next we get a basic :zoom output. A count of 3 is chosen to limit some frames with a huge number of bindings. The result is a simple, moderate :zoom output:
5): :zo :all t :count 3
[1c] cl-user(
Evaluation stack:
2 more newer frames ...
...
break "baz: ~s ~s" 10 ...)
(10 20)
->(foo eval ]
[... excl::%eval (foo 10 20))
(nil nil)
(tpl::read-eval-print-one-command
... more older frames ...6): [1c] cl-user(
Next, we want to see both catches and special-bindings in the moderate :zoom output. Note that the stack works its way away from the stack top or frontier as it goes lexically downward, or in other words, scanning upward visually takes you toward the direction of the calls: starting from the bottom, read-eval-print-one-command binds some variables, then calls eval, which binds a couple more variables, and eval then (indirectly) calls foo, which binds *foo*, then catches bar, after which an unwind-protect is entered within which bas is caught, *bas* is bound, and finally break is called. Note that bar is not on the stack - it has not yet been called:
6): :zo :catches t :specials t
[1c] cl-user(
Evaluation stack:
2 more newer frames ...
...
292, old value: ((#) (#))
Special excl::*restart-clusters* bound at bnp #x7ffffffc1970> last-bnp = 290 pc = 938
Catching <block @ #x7ffffffc19b8> last-bnp = 290 pc = 966
Catching <block @ break "baz: ~s ~s" 10 ...)
(290, old value: nil
Special *bas* bound at bnp = 288 pc = 407
Catching bas last-bnp = 288 pc = 523
Catching <unwind-protect> last-bnp 288, old value: nil
Special *bar* bound at bnp = 286 pc = 555
Catching bar last-bnp 286, old value: nil
Special *foo* bound at bnp 10 20)
->(foo eval ]
[... excl::%284, old value: nil
Special excl::%function-spec% bound at bnp 282, old value: nil
Special sys:*interpreter-environment* bound at bnp eval (foo 10 20))
(280, old value: ((#))
Special excl::*restart-clusters* bound at bnp 278, old value: nil
Special tpl::*tpl-time* bound at bnp nil nil)
(tpl::read-eval-print-one-command
... more older frames ...7): [1c] cl-user(
Next we get a verbose output. Note that both :catches
and :specials
set the zoom-print-catches and zoom-print-special-bindings variables, respectively, so they are sticky across :zoom invocations. Notes are provided below the output:
7): :zo :verbose t
[1c] cl-user(
Evaluation stack:
2 more newer frames ...
...
break
call to = "baz: ~s ~s"
optional arg: excl::datum rest excl::arguments = (10 20)
&function suspended at relative address 895
#x7ffffffc19b8> last-bnp = 290 pc = 966
Catching <block @ #x7ffffffc1970> last-bnp = 290 pc = 938
Catching <block @ 292, old value: ((#) (#))
Special excl::*restart-clusters* bound at bnp
----------------------------
->call to foo= 10
required arg: a = 20
required arg: b function suspended at relative address 394
286, old value: nil
Special *foo* bound at bnp = 286 pc = 555
Catching bar last-bnp 288, old value: nil
Special *bar* bound at bnp = 288 pc = 523
Catching <unwind-protect> last-bnp = 288 pc = 407
Catching bas last-bnp 290, old value: nil
Special *bas* bound at bnp
----------------------------eval)
(ghost call to excl::%
----------------------------eval
call to exp = (foo 10 20)
required arg: function suspended at relative address 107
282, old value: nil
Special sys:*interpreter-environment* bound at bnp 284, old value: nil
Special excl::%function-spec% bound at bnp
----------------------------
call to tpl::read-eval-print-one-command= nil
required arg: tpl::continuable = nil
required arg: tpl::inspecting = :unsupplied
&key tpl::skip-prompt = :unsupplied
&key tpl::input-stream = :unsupplied
&key tpl::output-stream = :unsupplied
&key tpl::terminal-stream function suspended at relative address 8172
278, old value: nil
Special tpl::*tpl-time* bound at bnp 280, old value: ((#))
Special excl::*restart-clusters* bound at bnp
----------------------------
... more older frames ...8): [1c] cl-user(
Note in the above display that the ordering of special bindings and catches is reversed from the moderate style. In a :verbose
style each frame tends to represent an individual function's structure, including arguments, and is separated by dashed lines from other functions' frames. Because it is less compact than the :moderate
style, functions tend to be seen individually and their contents are seen in the same ordering as they appear in actual source code (it is even more accurate to say that the output appears in the same nesting scope as the source code).
Finally, the list form of the :specials
mode is demonstrated, by enlarging the count and removing :verbose
and :catches
mode:
8): :zo :count t :specials (*standard-input* *standard-output*) :catches nil :verbose nil
[1c] cl-user(*standard-input* *standard-output*))
Evaluation stack:(looking for limited specials (
*standard-input* current value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special *standard-output* current value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special *standard-output* bound at bnp 338, old value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special *standard-input* bound at bnp 336, old value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special "return from break." ...)
(excl::read-eval-print-loop :continue-error-string *standard-input* bound at bnp 308, old value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special *standard-output* bound at bnp 306, old value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special "Break" #<simple-break @ #x100088d9c82> ...)
(excl::internal-invoke-debugger break "baz: ~s ~s" 10 ...)
(10 20)
->(foo eval ]
[... excl::%eval (foo 10 20))
(nil nil)
(tpl::read-eval-print-one-command *standard-output* bound at bnp 226, old value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special *standard-input* bound at bnp 224, old value: #<terminal-simple-stream [initial terminal io] fd 0/1 @ #x10000250862>
Special :level 0)
(excl::read-eval-print-loop
(tpl::top-level-read-eval-print-loop1)
(tpl:top-level-read-eval-print-loop)
((:runsys sys::lisp_apply))
[... sys::funcall-tramp ]0/1 @ #x10000250862> #<Function top-level-read-eval-print-loop>
(tpl:start-interactive-top-level #<terminal-simple-stream [initial terminal io] fd
...)t)
(excl::start-lisp-execution
((:runsys sys::lisp_apply))
[... excl::thread-bind-and-call* ]nil #<Function start-lisp-execution-0> ...)
(excl::thread-bind-and-call 9): [1c] cl-user(
Note that the bindings of these two variables are the same in all cases, which means that if either variable is modified with setq at the top level prompt and either a :pop or a :reset is performed, the original values will be restored. Note also that when specific variables are requested in the :specials
mode, the current value is first displayed, and if then there are any further bindings between the very top of the stack and the first displayed frame, the closest binding to the top displayed frame will be shown as well.
The :relative
argument causes :zoom to identify a frame with respect to the current frame. The identification is done with a number and either u
(meaning up or newer than the current frame) or d
(meaning down or older than the current frame).
1] USER(18): :zoom :relative t
[
Evaluation stack:
1u: (ERROR UNBOUND-VARIABLE :NAME ...)
->(EVAL FOO)
1d: (TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
2d: (TPL:START-INTERACTIVE-TOP-LEVEL
#<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @#x18ad06>
...)
The stack with :relative nil
does not display numbers next to frames.
1] USER(18): :zoom :relative nil
[
Evaluation stack:
(ERROR UNBOUND-VARIABLE :NAME ...)
->(EVAL FOO)
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
(TPL:START-INTERACTIVE-TOP-LEVEL#x18ad06> ...) #<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @
When sources are in a file and compilation is done so source-level debugging is enabled (usually a debug value of 3) and the current frames function has debugging information, the most recent source will be printed after the suspension location is printed. If the frame is the current frame (if :current would have printed this frame only) then the slide point is also set, and until any breakpoint actions are taken, the :slide command can be used to look at, move around, or set breakpoints in this function.
Consider the following function defined in a file ltest.cl:
eval-when (compile) (declaim (optimize debug)))
(defun ltest (x)
(with-open-file (s x)
(let ((y (read s)))
(format t "~s" y)))) (
We compile and load the file and make an erroneous call to the function ltest:
3): :cl ltest
cl-user(;;; Compiling file ltest.cl
;;; Writing fasl file /tmp/cfta106831758131
;;; Moving fasl file /tmp/cfta106831758131 to ltest.fasl
;;; Fasl write complete
; Fast loading /net/gemini/home/duane/ltest.fasl
4): (ltest 10)
cl-user(10 cannot be converted to a pathname.
Error: condition type: type-error]
[
Restart actions (select using :continue):0: Return to Top Level (an "abort" restart).
1: Abort entirely from this (lisp) process.
1] cl-user(5): :zo :all t :count 2
[
[ ... ]1] cl-user(6): :dn 2
[
Evaluation stack:
5 more newer frames ...
...
open 10)
(10)
->(ltest set to: (open x)
slide point
eval ]
[... excl::%eval (ltest 10))
(
... more older frames ...1] cl-user(7): :slide point
[
Function: #<Function ltest>:
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form4/34 1-2 5/34 nil/nil :early nil nil nil (open x)
5/34 0-2 6/39 nil/nil :ref nil 87 88 x
6/39 1-2 7/44 nil/nil nil :call nil nil (open x)
-> 7/44 1-2 8/44 nil/nil :const nil nil nil (quote t)
8/44 1-2 9/50 nil/nil :init nil nil nil ((s (open x)) s nil)
0 pc=39 rec=6
level=
Possible slide directions: (out across back)1] cl-user(8): :slide enclosed
[34
Sliding to #<Function ltest>:
Function: #<Function ltest>:
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form0/0 0 2/34 nil/nil nil nil 49 140 (defun ltest (x) (with-open-file (s x) (let ((y #)) (format t "~s" y))))
1/34 1 2/34 nil/nil nil nil (49) (140) (block ltest (with-open-file (s x) (let ((y #)) (format t "~s" y))))
2/34 0-1 4/34 nil/nil nil nil 68 139 (with-open-file (s x) (let ((y (read s))) (format t "~s" y)))
-> 3/34 1-2 4/34 nil/nil nil nil (68) (139) (let ((s (open x)) (#:with-open-file-abort-159 t)) ...)
4/34 1-2 5/34 nil/nil :early nil nil nil (open x)
0 pc=34 rec=2 starting at 68 ending at 139.
level=
Possible slide directions: (into out over across back)1] cl-user(9): [
Note that in the example above, the slide point was set to the open call (which was the call in which the error occurred) but that record has no position info (because it is part of the macro-expansion of the with-open-file form, and it doesn't replace the entire form). Using the :slide command with the enclosed option slides us outward conceptually to the point where the error is occurring in the text, somewhere between character positions 68 (inclusive) and 139 (exclusive) in the original source.
The :bt command provides a quick way to scan the stack.
Here is the effect of the :bt command on the example above (attempt to evaluate foo which has no value). In this case, :zoom is printing all frames (:all t) so :bt does as well.
1] USER(24): :bt
[
Evaluation stack:
EVAL <-
TPL::READ-EVAL-PRINT-ONE-COMMAND <-
EXCL::READ-EVAL-PRINT-LOOP <-
TPL::TOP-LEVEL-READ-EVAL-PRINT-LOOP1 <-
TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP <- APPLY <-
TPL:START-INTERACTIVE-TOP-LEVEL <- TPL::START-TOP-LEVEL <- EXCL::START-REBORN-LISP
The following variables affect the behavior of :zoom, usually by providing a default for an argument to :zoom. Note that the values of some of these variables are changed when :zoom is called with the argument associated with the variable specified. All symbols naming the variables are in the top-level
package (nicknamed tpl
).
*zoom-display* | Controls the maximum number of stack frames displayed by a
call to :zoom. Default for the :count
argument for :zoom. |
*zoom-print-circle* | cl:*print-circle* is bound to this during :zoom output. |
*zoom-print-level* | cl:*print-level* is bound to this during :zoom output. |
*zoom-print-length* | cl:*print-length* is bound to this during :zoom output. |
*zoom-print-long-string-length* | excl:*print-long-string-length* is bound to this during :zoom output. |
*zoom-print-special-bindings* | Default for the :specials argument to :zoom. Value reset if :zoom is called with :specials specified. |
*zoom-show-newer-frames* | If true, :zoom output shows frames newer than the current frame. |
*auto-zoom* | Controls whether frames are printed after moving up or down a frame or displaying the current frame (see :up, :dn, and :current). |
Here is how calls to undefined functions and calls with the wrong number of arguments, both in compiled code, are handled.
If, in compiled code, a call is made to an undefined function foo, a frame on the stack will be created to represent that call and it will show all of the arguments passed to foo.
For example:
user(4): (defun bar (x) (foo 1 2 3 4 x))
baruser(5): (compile 'bar)
compiling baruser(6): (bar 222)
the function foo is undefined.
Error: condition type: undefined-function]
[
Restart actions (select using :continue):0: Try calling foo again
1: Return a value instead of calling foo
2: Try calling a function other than foo
3: Setf the symbol-function of foo and call it again
1] user(7): :zoom
[
Evaluation stack:
error #<undefined-function @ #x10b9a86>)
(1 2 ...)
->(foo 222)
(bar eval (bar 222))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level#x2aa24e>
#<excl::bidirectional-terminal-stream @ #x2d96de>
#<Function top-level-read-eval-print-loop @
...)1] user(8): :cur
[1 2 3 4 222)
(foo 1] user(9): [
This frame can be restarted after defining foo:
1] user(10): (defun foo (&rest x) x)
[
foo1] user(11): :cur
[1 2 3 4 222)
(foo 1] user(12): :restart
[1 2 3 4 222)
(user(13):
In the wrong number of arguments situation (in our case, too few arguments) we again get an ordinary frame with the unsupplied arguments identified as :unknown
:
14): (defun baz (a b c)
USER(+ a b c))
(
BAZ15): (compile 'baz)
USER(
BAZ
NIL
NIL16): (baz 1 2)
USER(2 args, wanted 3 args.
Error: BAZ got condition type: PROGRAM-ERROR]
[1] USER(17): :zo
[
Evaluation stack:
(ERROR PROGRAM-ERROR :FORMAT-CONTROL ...)1 2 :UNKNOWN)
->(BAZ 1 2))
(EVAL (BAZ
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
(TPL:START-INTERACTIVE-TOP-LEVEL
#<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @#x487586>
#<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP @#x49114e> ...)
1] USER(18): [
The following commands are used to move the current frame pointer around the stack.
:dn | Move down the stack |
:up | Move up the stack. |
:bottom | Move to the bottom (oldest frame) of the stack. |
:top | Move to the top (newest frame) of the stack. |
:find | Move to the frame associated with the function-name argument to this command. |
These commands control which frames are displayed. Note that their effect can be overridden by calling :zoom with the :all argument specified t
.
:hide | Hide (i.e. :zoom should not display unless :all is specified t) things specified by the arguments. |
:unhide | Unhide (i.e. :zoom
with :all nil should display) things specified by the arguments. :unhide
called with no arguments causes the list of hidden objects to revert to the default set of
hidden objects. Note that if you want to see all frames in the stack, you should call :zoom with a true value for
the all argument. :unhide itself is not designed to unhide all
frames. |
;; In this example we hide all frames in the tpl package.
66): :unhide
USER(
Reverting hidden objects to initial state.67): :hide
USER(
hidden packages: LEP EXCL SYSTEM CLOS DEBUGGER
hidden packages internals: TOP-LEVEL
hidden functions: BLOCK APPLY
hidden frames: :INTERPRETER :EVAL :INTERNAL;;
;; We cause a BREAK and do a :zoom.
68): (break)
USER(the `break' function.
Break: call to
Restart actions (select using :continue):0: return from break.
69): :zoom :moderate t
[1c] USER(
Evaluation stack:
"call to the `break' function.")
(BREAK
->(EVAL (BREAK))
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
(TPL:START-INTERACTIVE-TOP-LEVEL#x19e67e>
#<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @ #x1c7ad6>
#<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP @
...);;
;; We hide all symbols in the TOP-LEVEL package.
70): :hide :package tpl
[1c] USER(71): :zoom
[1c] USER(
Evaluation stack:
"call to the `break' function.")
(BREAK
->(EVAL (BREAK))72): [1c] USER(
The following commands identify the current frame and the function called by the current frame or provide other information.
:current | Print the current stack frame and set the value of cl:*
to be the frame expression. |
:function | Print the function object called in the current stack frame
and set the value of cl:* to that object. |
:frame | Print out information about the current frame. |
:register | Print out the names and values of registers (or a specific register when passed its name), when the frame has saved context. Registers can be set with :set-register. |
;; In the example below, we use :current, :function and :frame.
;; Note that we use :hide :binding to limit the amount of output
;; printed in this example.
user(47): (defun foo (x)
let ((a (1+ x)))
(let ((b
(1+ a)))
(
progn (break "foo: ~a" b)
(
1 2 3 4 5 x a b)))))
(foo
foo
;; :hide :binding causes binding frames to be hidden.
user(48): :hide :binding
user(49): (foo 10)
12
Break: foo:
Restart actions (select using :continue):0: return from break.
user(50): :zoom
[1c]
Evaluation stack:
break "foo: ~a" 12)
(10)
->(foo eval (foo 10))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level#x280886>
#<excl::bidirectional-terminal-stream @ #x2ce19e>
#<Function top-level-read-eval-print-loop @
...);;
;; Print out the current frame.
user(51): :current
[1c] 10)
(foo
;; Print out the function object associated with the
;; current frame.
user(52): :function
[1c] #xaf983e>
#<Interpreted Function foo @
;; Print out interesting information about the frame. Note
;; that the information printed included frames hidden by the
;; :hide :binding command
user(53): :frame
[1c] 10)
Expression: (foo let ((a (1+ x))) ..)
Source code: (10
Local x: let ((b (1+ a))) ..)
Source code: (11
Local a: progn (break "foo: ~a" b) (foo 1 2 3 4 5 x a b))
Source code: (12
Local b: user(54): :res
[1c] user(55):
The commands described in this section operate in some fashion on frames. Note that local name information (which is very useful for debugging) is only loaded from a compiled file if the variable *load-local-names-info* is non-nil
when the compiled (fasl) file is loaded.
:local | This command causes the values of local variables for the current stack frame to be printed. The handling of local variables by the debugger is discussed under the heading Local variables and the debugger below for complete information and examples. |
:set-local | This command sets the value of the specified (as an argument) local variable to value, which is evaluated. The local variable is identified by name if interpreted, or name or index if compiled. |
:evalmode | This command allows evaluation in context, that is local variables can be used by name in forms typed to the top level. Setting such a local variable (with setq or setf) at the top level will change the value in the frame. Evaluation in context does not always work as desired (unavoidably so because of the lexically-scoped design of Lisp). It should only be used temporarily while debugging. |
Sometimes, particularly when a program is being run in batch mode (rather than interactively) or when the user of the program is not familiar with Lisp internals, you may want to generate a backtrace (as produced by :zoom) programmatically, perhaps writing it to a file as part of a problem report.
Functionality for doing this is in the autozoom module, with symbols in the top-level.debug
package. Load this module with the command
require :autozoom) (
If you want this functionality in an application, be sure to include :autozoom in the list of required files (the third required argument to generate-application).
There are two operators provided: the macro with-auto-zoom-and-exit and the function zoom. Both are defined in this section. The source for with-auto-zoom-and-exit is available in the file [Allegro directory]/src/autozoom.cl.
function, package: top-level.debug
Arguments: stream &rest zoom-command-args &key (count t) (all t) &allow-other-keys
Generate an execution stack trace to stream. This function is a wrapper for the top-level :zoom command and is intended to be used in applications that want to report unexpected errors. See with-auto-zoom-and-exit.
This function is only useful when used in conjunction with handler-bind. For example:
handler-bind
(error (lambda (condition)
((;; write info about CONDITION to a log file...
format *log-stream* \"Error in app: ~a~%\" condition)
(
;; send a zoom to the log file, too
(top-level.debug:zoom *log-stream*))))
(application-init-function))
The count and all keywords are passed to the :zoom command and are documented with that command.
macro, package: top-level.debug
Arguments: (place &key (count t) (all t) (exit t) no-unwind &body body
This macro generates an execution stack trace to place, which can be a stream, t
or a pathname. If a pathname, that file is opened, created or superseded as necessary, and used. This macro is a wrapper for top-level :zoom command and is intended to be used in applications that want to report unexpected errors.
The count and all keywords are passed to the :zoom command and are documented with that command.
A non-nil
value for exit causes the running application to terminate. The default value of exit is t
. (The normal situations for using this macro are (1) the program is running in batch mode and so there is no operator available to do anything; and (2) the program is being used by a user unfamiliar with the internals and so unable to do anything. As there is no one to take any action, this macro typically gets a backtrace, which is written to place, and then causes the program to exit.)
A non-nil
value for no-unwind causes unwind-protects in stack frames above the error to be ignored. (This argument is simply passed to exit which also accepts a no-unwind keyword argument.) no-unwind is ignored if exit is nil
. The default value of no-unwind is nil
(as it is for exit).
The source for this macro is provided with the Allegro CL distribution, in [Allegro directory]/src/autozoom.cl.
The local variables in a function include the arguments of the function and variables bound in a let or similar form within the function. Variables that are declared globally-special are not considered local variables, even if they are dynamically bound as an argument or in a let-binding.
The answer is both, either just in registers or on the stack and occasionally also in registers. Many architectures require at least one value to be in a register for their instructions to operate. So movement into and out of registers is inevitable. If a variable is stored onto the stack, it will be only in one non-volatile place for that variable's lifetime, regardless of where it may be temporarily located when it is needed in a register.
On certain platforms it is possible to store values of local variables in non-volatile registers rather than on the stack. This resulted in faster code execution but made debugging more difficult because (1) is was sometimes difficult to associate the local variable name with its value, and (2) local variables with disjoint life times (that is, one is not used at all until the other's use has ended) might share a non-volatile register so only the value of one is known when requested. When locals are on the stack, being sure of the variable name can still be a problem (but a more solvable one) while two locals never share a stack location.
As it happens, only Sparcs and RS/6000 among Allegro CL platforms allow local variables to be stored in non-volatile registers, and neither platform is supported in Allegro CL version 11.0 or later. Register allocation is relevant for 10.0 and 10.1 but those are less common platforms. So we are removing all discussion of storing locals in registers. The rest of this section is only about storing locals on the stack.
Allegro CL provides for examining and modifying the values of local variables in compiled code but this facility comes at a space cost (since extra information is needed in compiled code to provide the debugger with this information). Therefore, really useful local variable information in the debugger is only available in code compiled with the debug optimization quality set to 3 (assuming compiler switches have their default values). Further, local name information (which is very useful for debugging) is only loaded from a compiled file if the variable *load-local-names-info* is non-nil
when the compiled (fasl) file is loaded.
Consider this function in the file loc-fun.cl:
defun loc-fun (arg)
(let ((alfa (+ arg 1)) ;; alfa alive but see text
(+ arg 2)) ;; bravo alive but see text
(bravo (3)) ;; charlie alive
(charlie break "break1")
(setq charlie (+ alfa bravo charlie arg))
(
;; alfa and bravo are now dead
let ((delta 4) ;; delta alive
(5)) ;; echo alive
(echo break "break2")
(print (* delta echo charlie))))
(
;; charlie, delta, and echo are now dead
let ((alfa 10)) ;; (new) alpha alive
(let ((alfa (1+ alfa)) ;; (newest) alfa alive; one alfa shadowe.
(11) ;; (new) bravo alive
(bravo 12)) ;; (new) delta alive
(delta break "break3")
(print (+ alfa bravo delta)))
(break "break4")
(print alfa))) (
We compile and load the file. Note the debug optimization quality is 3 and *load-local-names-info* is true:
21): :opt
cl-user(
A response of ? gets help on possible answers.
[...]optimize setting is
Compiler declaim (optimize (safety 1) (space 1) (speed 1) (debug 3)
(compilation-speed 1)))
(22): *load-local-names-info*
cl-user(t
23): :cf loc-fun
cl-user(; Fast loading local-fun.fasl
25): cl-user(
All local variables have ranges when they are alive (that is in use). Before they are used and after their last use, they are dead, which means that their value is not important because it is never looked at. We have marked our function above up to show the live ranges of our local variables. Note that alfa and bravo are technically not alive until charlie gets its value since if the charlie binding was (* *alfa* *bravo* 3)
, the values of alfa and bravo in that form would be the lexical values prior to the start of the let form but the compiler can see that the symbols alfa and bravo are not used in that way so they come to life sooner. (If we used let* rather than let, alfa and bravo would always come to life when bound as appearances in the charlie binding form would refer to the bound values of alfa and bravo.)
Before we discuss local variables in detail, we should remark that the information necessary to provide information on local variables for debugging takes up a fairly significant amount of space in the image. If you have completed debugging and you want to dump an image (with dumplisp, you may want to discard this information before dumping (to save space in the dumped image). The function discard-local-name-info will do that.
nil
when compiled (fasl) files created with local variable information are loaded.The names of local variables are not necessary in compiled code (the locations are sufficient). Saving names takes space. The compiler will only save names when the speed, safety, and debug compiler optimization qualities are such that the compiler switch save-local-names-switch is either a function returning true or is itself true. It is usual when developing code (so debugging is likely) to compile with that switch true, so names are available. In production code, it is usual to compile so names are not saved, to save space. When debugging code where names are not saved, locals are identified by a numerical index.
Further, local name information, if stored in a compiled file, is only loaded if the value of the variable *load-local-names-info* is non-nil
when compiled (fasl) files created with local variable information are loaded.
The advantage of register access is that it is much faster than stack access. But there is also a downside: there may not be a variable name associated with the register, because it is transient (because of efficient compilation) and never stored on the stack. This means that debugging will be harder.
We will get back to the advantages of placing values only in registers below, but we want to emphasize the debugging costs so that they are clear. Consider this example:
defun blah (&rest format-args)
(let ((result (apply #'format nil format-args)))
(read-from-string result)))
(
compile 'blah)
(
"~a/0" 42) (blah
Evaluating the last form will cause a divide by zero (result
is the string "42/0", which is then read). But the variable named result
never shows up on the stack because it is transiently used as the return value from an apply and as the first argument to read-from-string, and thus does not even move from its register location - in all Allegro CL implementations, the first return-value register is also the first argument register; this provides for extremely efficient code. Therefore, when debugging (after the divide by zero error is signaled), you want to look at the value of result
but will not be able to see it under that name.
If you want to force storing values on the stack, you can trace read-from-string with a form like
trace (read-from-string :inside blah)) (
Or you can use the :break top-level command to set an instruction-level breakpoint and setting :ldb mode on will allow single-stepping to track the variables (including the arg0/result register) if the user knows how to look at the disassembler output and interpret assembler code.
Or you can redefine blah
to force result
onto the stack so that it will show up as a local variable:
defun blarg () nil)
(
defun blah (&rest format-args)
(let ((result (apply #'format nil format-args)))
(
(blarg)read-from-string result))) (
Inserting the call to blarg
forces result
onto the stack (because the register holding its value cannot be protected during a function call). result
will thus show up in debugger when the read-from-string errors. Note, however, that if local-scopes are compiled in, then the debugger will not show the variable named result
, because it is a dead local (its last usage is as an argument to read-from-string). To see the name in that situation, the :d option to the :local debugging command can be used.
Now back to the advantages of using registers to hold values. Because of the very significant performance boost, registers are used whenever possible. Further, when registers are used, the compiler will analyze the code in order to determine the live ranges of locals and have locals with disjoint live ranges share the same register (live range is defined just below). This allows maximum use of registers.
Locals stored on the stack do not share locations -- that is, each local stored on the stack is assigned its own location. This is true on machines where all locals are stored on the stack and on machines where some locals are stored in registers and others are stored on the stack. It may be that sharing locations on the stack would be a desirable optimization (because of reduced stack growth). However, it has not been implemented.
The examples in the rest of this section are taken from a Sparc machine and therefore registers are used for locals. All the behavior described applies to any machine (whether or not registers are used to store locals) except maybe the concept of register sharing.
When compiling a function, the compiler tries to determine when a local variable is first defined (typically, when it is bound) and when a local variable is last used. The time that the local is used is called the live range of the variable.
When locals can be stored in registers, if two locals have disjoint live ranges, the compiler may use the same register to store the values of both variables.
Consider this definition:
defun foo (lis)
(let ((a 10) (b 9))
(pprint (list a b lis))
(list-length lis))) (
The function list-length is in the tail position, which means that what it returns will be returned by foo, and so no information about foo is needed on the stack. The compiler will always arrange that the stack is cleared of all but the tail position function when possible (regardless of whether it tail merges or not). Therefore, the form (foo 10)
will cause an error when list-length is applied to 10 -- which is not a list -- but the values of locals a and b will not be available.
The behaviors of live ranges and their interactions with the :local and :set-local top-level commands are best shown by example. Consider the following function definition in file fun-loc.cl:
defun loc-fun (arg)
(let ((alfa (+ arg 1)) ;; alfa alive but see text
(+ arg 2)) ;; bravo alive but see text
(bravo (3)) ;; charlie alive
(charlie break "break1")
(setq charlie (+ alfa bravo charlie arg))
(
;; alfa and bravo are now dead
let ((delta 4) ;; delta alive
(5)) ;; echo alive
(echo break "break2")
(print (* delta echo charlie))))
(
;; charlie, delta, and echo are now dead
let ((alfa 10)) ;; (new) alfa alive
(let ((alfa (1+ alfa)) ;; (newest) alfa alive; one alfa shadows.
(11) ;; (new) bravo alive
(bravo 12)) ;; (new) delta alive
(delta break "break3")
(print (+ alfa bravo delta)))
(break "break4")
(print alfa))) (
The comments show where the various locals become alive and when they die. There is an issue about where alfa and bravo become alive. Strictly speaking, alfa, bravo, and charlie all become alive when the binding form completes. That is guaranteed in let so if alfa and bravo had lexically apparent values from above the let form, they could be used later in the binding form. In fact that does not happen, so the compiler may make the assignment at the position indicated rather than later. This is highly architecture-dependent and might or might not occur, depending on debug and speed settings.
The breaks are inserted to allow us to examine what is happening at various points.
Compiling this function with debug 3
ensures that local names are saved. But also, we need to be sure local names are also loaded when the file is loaded.
1): (declaim (optimize debug))
cl-user(t
2): (setq *load-local-names-info* t *load-source-file-info* t *load-source-debug-info* t)
cl-user(t
3): (setq *record-source-file-info* t)
cl-user(t
4): :cf loc-fun
cl-user(;;; Compiling file loc-fun.cl
;;; Writing fasl file /tmp/cfta278160006231
;;; Moving fasl file /tmp/cfta278160006231 to loc-fun.fasl
;;; Fasl write complete
5): :ld loc-fun
cl-user(; Fast loading /net/gemini/home/duane/loc-fun.fasl
6): (loc-fun 22)
cl-user(; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 190 (inclusively) and 216 (exclusively)
;[form: "(+ alfa bravo charlie arg)"]:
Break: break1
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
7): [1c] cl-user(
We are now in the first break; :loc shows what variables are alive:
7): :loc
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): arg: 22
3(Local): charlie: 3
4(Local): alfa: 23
6(Local): bravo: 24
8): [1c] cl-user(
And the :d option shows all of them:
8): :loc :d
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): arg: 22
2(Local): (:dead alfa): 1
3(Local): charlie: 3
4(Local): alfa: 23
5(Local): (:dead delta): nil
6(Local): bravo: 24
7(Local): (:dead alfa): 0
8(Local): (:dead bravo): 0
9): [1c] cl-user(
Here we start by trying to set a variable named alfa, but since there are 3 such variables, and since we want to include the dead ones, it refuses:
9): :set-loc :d alfa 10
[1c] cl-user(
There are multiple matching variables named alfa that make setting one valuethe local (integer) index to specify the variable to set:
ambiguous. Use
2(Local): (:dead alfa): 1
4(Local): alfa: 23
7(Local): (:dead alfa): 0
set the variable.
Couldn't 10): [1c] cl-user(
But because delta, which is also a dead variable, is the only one of that name, it works (silently):
10): :set-loc :d delta 10
[1c] cl-user(11): [1c] cl-user(
... and can be called back up again:
11): :loc :d delta
[1c] cl-user(10
12): :loc :d
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): arg: 22
2(Local): (:dead alfa): 1
3(Local): charlie: 3
4(Local): alfa: 23
5(Local): (:dead delta): 10
6(Local): bravo: 24
7(Local): (:dead alfa): 0
8(Local): (:dead bravo): 0
13): [1c] cl-user(
Now we look to see what delta is, even though it is dead. Because there is at least one dead variable named delta and no live ones, we get a note to that effect:
13): :loc delta
[1c] cl-user(values. There is at least one variable named delta that is dead.
No live the :d option to :local see any dead variables.
Use 14): [1c] cl-user(
Let's move on to the second break:
14): :loc :d delta
[1c] cl-user(10
15): :cont
[1c] cl-user(; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 345 (inclusively) and 375 (exclusively)
;[form: "(print (* delta echo charlie))"]:
Break: break2
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
16): [1c] cl-user(
Note that only charlie is live (besides arg - arguments are always live):
16): :loc
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
3(Local): charlie: 72
17): [1c] cl-user(
Note that if we ask for a name that is not known, we see no output (as we did before this change):
17): :loc bogus
[1c] cl-user(18): [1c] cl-user(
Here we look at alfa, which has no live values, just like delta earlier.
18): :loc alfa
[1c] cl-user(values. There is at least one variable named alfa that is dead.
No live the :d option to :local see any dead variables.
Use 19): [1c] cl-user(
And trying to set this dead variable fails, because no values are live. But using the :d option also fails, because there are multiple dead variables named alfa:
19): :set-loc alfa 10
[1c] cl-user(values to set. There is at least one variable named alfa that is dead.
No live the :d option to :local or :set-local to set or see any dead variables.
Use set the variable.
Couldn't 20): :set-loc :d alfa 10
[1c] cl-user(
There are multiple matching variables named alfa that make setting one valuethe local (integer) index to specify the variable to set:
ambiguous. Use
2(Local): (:dead alfa): 1
4(Local): (:dead alfa): 23
7(Local): (:dead alfa): 0
set the variable.
Couldn't 21): [1c] cl-user(
Instead, following the instructions, the desired variable can be set using its index (in this case we chose 7):
21): :set-loc :d 7 10
[1c] cl-user(22): :loc :d
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): (:dead arg): 22
2(Local): (:dead alfa): 1
3(Local): charlie: 72
4(Local): (:dead alfa): 23
5(Local): (:dead delta): 10
6(Local): (:dead bravo): 24
7(Local): (:dead alfa): 10
8(Local): (:dead bravo): 0
23): :cont
[1c] cl-user(
1440 ; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 631 (inclusively) and 659 (exclusively)
;[form: "(print (+ alfa bravo delta))"]:
Break: break3
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
24): :loc
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
2(Local): alfa: 10
5(Local): delta: 12
7(Local): alfa: 11
8(Local): bravo: 11
25): [1c] cl-user(
Note that we have two live variables named alfa. This is because alfa has nested bindings, and although one is not visible at this point in the program, it still is live.
25): :loc :d
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): (:dead arg): 22
2(Local): alfa: 10
3(Local): (:dead charlie): 72
4(Local): (:dead alfa): 23
5(Local): delta: 12
6(Local): (:dead bravo): 24
7(Local): alfa: 11
8(Local): bravo: 11
26): [1c] cl-user(
Here we try to set a value for alfa, which has two live locations.
26): :set-loc alfa 15
[1c] cl-user(
There are multiple matching variables named alfa that make setting one valuethe local (integer) index to specify the variable to set:
ambiguous. Use
2(Local): alfa: 10
7(Local): alfa: 11
set the variable.
Couldn't 27): [1c] cl-user(
In this case, using the index works just as in the case of the multiple dead variables.
27): :set-loc 7 15
[1c] cl-user(28): :loc
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
2(Local): alfa: 10
5(Local): delta: 12
7(Local): alfa: 15
8(Local): bravo: 11
29): [1c] cl-user(
Let's continue on to the last break:
29): :cont
[1c] cl-user(
38 ; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 686 (inclusively) and 698 (exclusively)
;[form: "(print alfa)"]:
Break: break4
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
30): [1c] cl-user(
Note that the last break occurs after the scope of the inner alfa has closed, so that inner alfa (which we forced to 15 earlier) is now dead, and only the outer variable is alive with its original value of 10:
30): :loc
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
2(Local): alfa: 10
31): [1c] cl-user(
And the newly dead alfa with value 15 can still be seen as dead here:
31): :loc :d
[1c] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): (:dead arg): 22
2(Local): alfa: 10
3(Local): (:dead charlie): 72
4(Local): (:dead alfa): 23
5(Local): (:dead delta): 12
6(Local): (:dead bravo): 24
7(Local): (:dead alfa): 15
8(Local): (:dead bravo): 11
32): [1c] cl-user(
Unfortunately there is no way to know what the hierarchy of variables is after compilation; the debug structures don't currently support info that describes hierarchy.
Note that the above breaks in function operation have all been made via calls to the break function. Executing the :zoom command shows that break is the next function on the stack after the function we're examining:
32): :zo :all t
[1c] cl-user(
Evaluation stack:
"return from break." ...)
(excl::read-eval-print-loop :continue-error-string "Break" #<simple-break @ #x1000886a3c2> ...)
(excl::internal-invoke-debugger break "break4")
(22)
->(loc-fun set to: (break "break4")
slide point
eval ]
[... excl::%eval (loc-fun 22))
(nil nil)
(tpl::read-eval-print-one-command :level 0)
(excl::read-eval-print-loop
(tpl::top-level-read-eval-print-loop1)
(tpl:top-level-read-eval-print-loop)
... more older frames ...33): [1c] cl-user(
However, if we start over, and use the source-level debugger to step through source arbitrarily (note in this case we're going to stop before the first let form in the function):
1): (setq *load-local-names-info* t *load-source-file-info* t *load-source-debug-info* t)
cl-user(t
2): :ld loc-fun
cl-user(; Fast loading /net/gemini/home/duane/loc-fun.fasl
3): :br loc-fun :pos 23
cl-user(35
Adding #<Function loc-fun>: "loc-fun.cl":
Function: #<Function loc-fun> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form1/35 1 2/35 nil/nil nil nil (0) (700) (block loc-fun (let ((alfa (+ arg 1)) (bravo (+ arg 2)) (charlie 3)) (break "break1") ...) ...)
2/35 1 3/35 nil/nil nil nil nil nil (progn (let ((alfa (+ arg ...)) (bravo (+ arg ...)) ...) ...) ...)
3/35 0-1 4/35 nil/nil nil nil 23 377 (let ((alfa (+ arg 1)) (bravo (+ arg 2)) (charlie 3)) (break "break1") ...)
-> 4/35 0-1 6/35 nil/nil nil nil 35 44 (+ arg 1)
5/35 1-2 6/35 nil/nil :early nil (35) (44) (excl::+_2op arg 1)
0 pc=35 rec=3 in #P"loc-fun.cl" starting at 23 ending at 377.
level=
Possible slide directions: (out over across back)0
Current step/slide type: over/4): :ldb t
cl-user(ldb] cl-user(5): (loc-fun 22)
[
= #<Function loc-fun>, pc=35
Hit breakpoint at func "loc-fun.cl":
Function: #<Function loc-fun> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form1/35 1 2/35 nil/nil nil nil (0) (700) (block loc-fun (let ((alfa (+ arg 1)) (bravo (+ arg 2)) (charlie 3)) (break "break1") ...) ...)
2/35 1 3/35 nil/nil nil nil nil nil (progn (let ((alfa (+ arg ...)) (bravo (+ arg ...)) ...) ...) ...)
3/35 0-1 4/35 nil/nil nil nil 23 377 (let ((alfa (+ arg 1)) (bravo (+ arg 2)) (charlie 3)) (break "break1") ...)
-> 4/35 0-1 6/35 nil/nil nil nil 35 44 (+ arg 1)
5/35 1-2 6/35 nil/nil :early nil (35) (44) (excl::+_2op arg 1)
0
level=
Possible slide directions: (out over across back)0
Current step/slide type: over/6): [ldb-step] cl-user(
Note here that the :zoom output shows the lisp-breakpoint handler as next on the stack. Because of this, local information is enhanced significantly, using source-debug information as the enhancement:
6): :zo :all t
[ldb-step] cl-user(
Evaluation stack:
(excl::read-eval-print-loop :stepping :ldb-step)
[... sys::funcall-tramp ]nil)
(excl::ldb-lisp-prompt-loop #<#>
[... sys::funcall-tramp ]35 ...)
(sys::lisp-breakpoint #<Function loc-fun> "lisp_breakpoint")
(sys::.lisp-breakpoint-runtime-handler 22)
->(loc-fun set to: (block loc-fun
slide point let (# # #)
(break "break1")
(setq charlie #)
(let # # #))
(let (#)
(let # # #)
(break "break4")
(print alfa)))
(
eval ]
[... excl::%eval (loc-fun 22))
(nil nil)
(tpl::read-eval-print-one-command
(excl::read-eval-print-loop :stepping :ldb)t)
(tpl::ldb-command
... more older frames ...7): [ldb-step] cl-user(
Note first that the information presented is the same as before (although since we've stopped before the let, none of the variables to be bound have yet become alive):
7): :loc
[ldb-step] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): arg: 22
8): [ldb-step] cl-user(
However, if we look at dead variables, the story is a bit different:
8): :loc :d
[ldb-step] cl-user(
Compiled lexical environment:0(Required): arg: 22
1(Local): arg: 22
2(Local): (:dead alfa): 1
3(Local): (:dead charlie): nil
4(Local): (:dead alfa): nil
5(Local): (:dead delta): nil
6(Local): (:dead bravo): *print-circle*
7(Local): (:dead alfa): 0
8(Local): (:dead bravo): 0
9(Reg/%rdi): (:dead (:param 0)): 22
10(Reg/%rsi): (:dead (:param 1)): nil
11(Local): (:dead nil): (22)
12(Local): (:dead nil): 22
13(Local): (:dead nil): nil
14(Local): (:dead nil): nil
15(Reg/%rdi): (:dead nil): 22
16(Reg/%rdi): (:dead arg): 22
17(Reg/%rdi): (:dead nil): 22
18(Reg/%rsi): (:dead nil): nil
19(Reg/%rdi): (:dead alfa): 22
20(Reg/%rdi): (:dead nil): 22
21(Reg/%rsi): (:dead nil): nil
22(Reg/%rdi): (:dead bravo): 22
23(Reg/%r13): (:dead nil): (22)
24(Reg/%r13): (:dead nil): (22)
25(Reg/%rsi): (:dead bravo): nil
26(Reg/%rdi): (:dead nil): 22
27(Reg/%r12): (:dead nil): nil
28(Reg/%r12): (:dead nil): nil
29(Reg/%rsi): (:dead charlie): nil
30(Reg/%rdi): (:dead nil): 22
31(Reg/%r13): (:dead nil): (22)
32(Reg/%rsi): (:dead arg): nil
33(Reg/%rdi): (:dead charlie): 22
34(Reg/%rdi): (:dead nil): 22
35(Reg/%rdi): (:dead nil): 22
36(Reg/%rdi): (:dead nil): 22
37(Reg/%rsi): (:dead nil): nil
38(Reg/%rdi): (:dead alfa): 22
39(Reg/%r13): (:dead nil): (22)
40(Reg/%r13): (:dead nil): (22)
41(Reg/%rsi): (:dead bravo): nil
42(Reg/%rdi): (:dead nil): 22
43(Reg/%r12): (:dead nil): nil
44(Reg/%r12): (:dead nil): nil
45(Reg/%rsi): (:dead delta): nil
46(Reg/%rdi): (:dead nil): 22
47(Reg/%rdi): (:dead alfa): 22
9): [ldb-step] cl-user(
So what is different? When at a Lisp breakpoint, any source-debug-info that the current function has is included in the data used by the :local and :set-local commands. We can see variable transitions by using print-function-meta-info (although it is not shown here because the display is huge):
t :vars t) (print-function-meta-info 'loc-fun :mixed
As breakpoints are single-stepped through and registers become alive, they are displayed in the local command, and can be changed by :set-local.
Let's skip through all of the single-stepping and break explicitly on one particular breakpoint record: record 85, which has been selected for this architecture based on the output of print-function-meta-info:
9): :br rec 85
[ldb-step] cl-user(532
Adding #<Function loc-fun>: "loc-fun.cl":
Function: #<Function loc-fun> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form83/518 0-1 84/518 nil/nil :early nil 686 698 (print alfa)
84/518 0-1 85/532 nil/nil :ref nil 693 697 alfa
85/532 0-1 86/535 nil/nil nil :call 686 698 (print alfa)
-> 86/535 0-1 nil/nil nil/nil nil :noreturn nil nil (defun loc-fun (arg) ...)
87/560 1-2 8/82 nil/nil :clone nil (35) (44) (excl::+_2op arg 1)
0
level=
Possible slide directions: (out across back)0
Current step/slide type: over/10): [ldb-step] cl-user(
Note that this record occurs immediately after referencing the variable alfa (it is close to the end of the function). Since it is after all of the calls to :break, we'll also have to continue from each of those breaks before we can actually hit that breakpoint:
10): :step cont
[ldb-step] cl-user(; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 190 (inclusively) and 216 (exclusively)
;[form: "(+ alfa bravo charlie arg)"]:
Break: break1
Restart actions (select using :continue):0: return from break.
1: Return to Debug Level 1 (an "abort" restart).
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
11): :cont
[2c] cl-user(; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 345 (inclusively) and 375 (exclusively)
;[form: "(print (* delta echo charlie))"]:
Break: break2
Restart actions (select using :continue):0: return from break.
1: Return to Debug Level 1 (an "abort" restart).
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
12): :cont
[2c] cl-user(
1440 ; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 631 (inclusively) and 659 (exclusively)
;[form: "(print (+ alfa bravo delta))"]:
Break: break3
Restart actions (select using :continue):0: return from break.
1: Return to Debug Level 1 (an "abort" restart).
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
13): :cont
[2c] cl-user(
34 ; While evaluating #'loc-fun in #P"loc-fun.cl"
; between file character positions 686 (inclusively) and 698 (exclusively)
;[form: "(print alfa)"]:
Break: break4
Restart actions (select using :continue):0: return from break.
1: Return to Debug Level 1 (an "abort" restart).
2: Return to Top Level (an "abort" restart).
3: Abort entirely from this (lisp) process.
14): :cont
[2c] cl-user(
= #<Function loc-fun>, pc=532
Hit breakpoint at func "loc-fun.cl":
Function: #<Function loc-fun> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form83/518 0-1 84/518 nil/nil :early nil 686 698 (print alfa)
84/518 0-1 85/532 nil/nil :ref nil 693 697 alfa
85/532 0-1 86/535 nil/nil nil :call 686 698 (print alfa)
-> 86/535 0-1 nil/nil nil/nil nil :noreturn nil nil (defun loc-fun (arg) ...)
87/560 1-2 8/82 nil/nil :clone nil (35) (44) (excl::+_2op arg 1)
0
level=
Possible slide directions: (out across back)0
Current step/slide type: over/15): [ldb-step] cl-user(
And now that we have actually hit that breakpoint, we can examine locals:
15): :loc
[ldb-step] cl-user(
Compiled lexical environment:0(Required): arg: 22
2(Local): alfa: 10
47(Reg/%rdi): alfa: 10
16): [ldb-step] cl-user(
And if we try setting the local, it is ambigouous:
16): :set-loc alfa 12
[ldb-step] cl-user(
There are multiple matching variables named alfa that make setting one valuethe local (integer) index to specify the variable to set:
ambiguous. Use
2(Local): alfa: 10
47(Reg/%rdi): alfa: 10
set the variable.
Couldn't 17): [ldb-step] cl-user(
So we set the register based on its local index, and verify that it has changed:
17): :set-loc 47 12
[ldb-step] cl-user(18): :loc
[ldb-step] cl-user(
Compiled lexical environment:0(Required): arg: 22
2(Local): alfa: 10
47(Reg/%rdi): alfa: 12
19): [ldb-step] cl-user(
Finally, if we step past that breakpoint, we now see that the value printed and then returned is in fact the new value we had set:
19): :step cont
[ldb-step] cl-user(
12
12
ldb] cl-user(20): [
The :boe command allows you to break on exit, that is to stop execution when a frame is returned from.
Here is an example of using the :boe command.
1): (defun foo (a b) (let ((c (bar a b))) c))
cl-user(
foo2): (defun bar (a b) (break) (list a b))
cl-user(
bar3): (compile 'foo)
cl-user(
foonil
nil
4): (compile 'bar)
cl-user(
barnil
nil
5): (foo 1 2)
cl-user(the `break' function.
Break: call to
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
6): :zo
[1c] cl-user(
Evaluation stack:
break)
(1 2)
->(bar 1 2)
(foo eval ]
[... excl::%eval (foo 1 2))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level0/1 @
#<terminal-simple-stream [initial terminal io] fd #x100006a7182>
#<Function top-level-read-eval-print-loop> ...)7): :cont
[1c] cl-user(1 2)
(8): (foo 1 2)
cl-user(the `break' function.
Break: call to
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
9): :dn
[1c] cl-user(
Evaluation stack:
break)
(1 2)
(bar 1 2)
->(foo eval ]
[... excl::%eval (foo 1 2))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level0/1 @
#<terminal-simple-stream [initial terminal io] fd #x100006a7182>
#<Function top-level-read-eval-print-loop> ...)10): :boe
[1c] cl-user(11): :zo
[1c] cl-user(
Evaluation stack:
break)
(1 2)
(bar 1 2)
*->(foo eval (foo 1 2))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level0/1 @
#<terminal-simple-stream [initial terminal io] fd #x100006a7182>
#<Function top-level-read-eval-print-loop> ...)12): :cont
[1c] cl-user(1 value: (1 2)> after exiting from function.
Break: Returning #<
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this (lisp) process.
13): :zo
[1c] cl-user(
Evaluation stack:
break "Returning ~s after exiting from function." #<1 value: #>)
(eval (foo 1 2))
->(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level0/1 @
#<terminal-simple-stream [initial terminal io] fd #x100006a7182>
#<Function top-level-read-eval-print-loop> ...)14): [1c] cl-user(
The top-level commands :return and :restart both attempt to restart evaluation of a broken process from the current frame (see :current). :return sequentially evaluates the arguments provided and returns their values from the current stack frame. The function associated with the current stack frame is not executed, and its arguments are not again evaluated.
The :return command is useful in situations similar to the following. Suppose that in your code you had taken the log of a value when you meant to take the exp of the value. If you make the call to log be the current stack frame, then typing
exp value) :return (
will cause the computation to continue as if the code were correct to begin with, that is, exp of value will be calculated and returned from the current frame. The log function will not be re-executed.
:restart works on frame objects. A frame object is a list whose first element is a function and whose remaining elements are the evaluated arguments to the function. The top-level command :zoom prints a backtrace of the current execution stack. If :zoom is printing in moderate mode, it prints frame objects. (In brief mode, it simply prints the function name. In verbose mode, more than the frame object is printed.) :zoom and related commands (:find, :dn, :up, :top, etc.) all display the stack and identify one stack frame as the current stack frame. The value of the lisp variable * is set to the identified frame as part of the action of :zoom or a related command. Thus, if :zoom is printing in moderate mode, * will be set to a frame object after :zoom or a related command completes.
The argument to the :restart command must evaluate to a frame object (or nil
, indicating that the current frame object should be used unchanged). Calling :restart restarts computation, replacing the current frame with the argument frame and continuing from there. Contrast this with :return which returns from a frame with a specified value (but does not re-evaluate the current frame). The two commands, :return and :restart can behave quite differently. We will give an example below.
As an example of :restart called with no arguments, suppose you define the following functions:
defun baz (n) (bar (+ n 3)))
(defun bar (n) (+ (goo n) 5))
(defun foo (x) (* x x)) (
We have misspelled foo as goo in the definition of bar. If we evaluate
7) (baz
we get an error since goo is undefined. The stack as printed by :zoom contains the frame
10) (bar
We can then correct the definition of bar and load in just the new definition using the Emacs-Lisp interface. Next, if we make
10) (bar
the current frame (using :dn or :find), we can call :restart without arguments (or with nil
as an argument) and the computation will continue, returning the correct answer (105).
As an example of calling :restart with an argument, consider the following. Suppose bar and foo are defined as:
defun bar () (+ (hoo 1 2 3) (hoo 4 5 6)))
(defun foo (a b c) (+ a b c)) (
Here, the call to hoo in bar is a misprint: it should be a call to foo. If we evaluate (bar)
, we again get an undefined function error. If we call :zoom (with its :all argument specified as t
so all frames are printed), we see the following frame:
eval (+ (hoo 1 2 3) (hoo 4 5 6))) (excl::%
(You may have to set the values of *zoom-print-level* and *zoom-print-length* to nil
to get the entire frame without # marks or suspension points.) If we make that frame the current frame (so it is the value of cl:*
) and then evaluate
subst 'foo 'hoo *) :restart (
the frame will be re-evaluated so that foo is called rather than hoo and the correct result (21) will be returned.
The argument to :restart must evaluate to a frame object and the arguments
in a frame object (the elements of the cdr) will not be further evaluated. Thus the following are not equivalent:
+ 1 2 3 5)
(+ 1 2 3 (- 6 1)) (
The first will evaluate correctly. The second will bomb since a list is not a legal argument to +.
One might think at first that :restart called with a frame object and :return called with a form which, when the arguments are evaluated, is the same since the frame object will have the same result. This is not, however, correct.
The bindings set up on the stack are (in some cases) handled differently by the two :restart and :return. Like many binding issues in Lisp, the example is not immediately intuitive (at least to Lisp beginners). Let us give an example and then discuss it. Consider the following functions:
defun bar ()
(let ((*special* 1))
(declare (special *special*))
(5)))
(foo defun foo (x)
(let ((*special* (1+ *special*)))
(declare (special *special*))
(if (eq x 5) (break "sorry, bad number") *special*))) (
When we evaluate (bar)
, foo will be called with argument 5 and break will put Lisp into a break loop. We decide to go to the frame where foo is called and recall foo with a different argument. Here is the stack after the break. We go down two frames to reach the frame where foo is called.
;; Note: this manual is generic over several implementations.
;; The stack may differ from one implementation to another
;; so the stack you see may differ in detail from what is
;; reproduced here.
4): (bar)
USER(number
Break: sorry, bad
Restart actions (select using :continue):0: return from break.
5): :zoom
[1c] USER(
Evaluation stack:
"sorry, bad number")
(BREAK 5) (BREAK "sorry, bad number") ...)
->(IF (EQ X
(LET (#) (DECLARE #) ...)5)
(FOO
(LET (#) (DECLARE #) ...)
(BAR)
(EVAL (BAR))
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
(TPL:START-INTERACTIVE-TOP-LEVEL#x162f5e>
#<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @ #x282156>
#<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP @
...)
... more older frames ...6): :dn 2
[1c] USER(
Evaluation stack:
"sorry, bad number")
(BREAK 5) (BREAK "sorry, bad number") ...)
(IF (EQ X
(LET (#) (DECLARE #) ...)5)
->(FOO
(LET (#) (DECLARE #) ...)
(BAR)
(EVAL (BAR))
(TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
(TPL:START-INTERACTIVE-TOP-LEVEL#x162f5e>
#<EXCL::BIDIRECTIONAL-TERMINAL-STREAM @ #x282156>
#<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP @
...)
... more older frames ...7): [1c] USER(
Let us compare the following two commands that could be entered at this point. We show the commands and what is returned and then we explain why the returned values are different.
6) -> 3
:return (foo 6) -> 2 :restart '(foo
Both avoid the call to break since the argument to foo is not 5. However, the :return command causes bar to return the value 3 while the :restart command causes bar to return the value 2. The binding of *special*
which took place in foo before the call to break is not undone by :return. It is undone by :restart.
The moral is that you can use :return with a form argument when you know what value is expected and there are no side effects that you care or are concerned about. You should use :restart when side effects are important.
In the following example, we show :zoom displaying the evaluation stack, and show how to use some of the other stack manipulation commands.
;; Note: this manual is generic over several implementations.
;; The stack may differ from one implementation to another
;; so the stack you see may differ in detail from what is
;; reproduced here.
user(2): (defun func (x) (* x (bar x)))
funcuser(3): (defun bar (x) (car x))
baruser(4): (func 10)
the car of 10 which is not a cons.
Error: Attempt to take condition type: simple-error]
[1] user(5): :zo
[
Evaluation stack:
error simple-error :format-control ...)
(car 10)
->(10)
(bar 10)
(func eval (func 10))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level#x2a21de>
#<excl::bidirectional-terminal-stream @ #x2d59c6>
#<Function top-level-read-eval-print-loop @
...)1] user(6): :find bar
[
Evaluation stack:
error simple-error :format-control ...)
(car 10)
(10)
->(bar 10)
(func eval (func 10))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level#x2a21de>
#<excl::bidirectional-terminal-stream @ #x2d59c6>
#<Function top-level-read-eval-print-loop @
...)1] user(7): :return 5
[;; :RETURN simply returns 5 from the frame
;; resulting in FUNC returning 50.
50
user(8): (func 10)
the car of 10 which is not a cons.
Error: Attempt to take condition type: simple-error]
[1] user(9): :find func
[
Evaluation stack:
error simple-error :format-control ...)
(car 10)
(10)
(bar 10)
->(func eval (func 10))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level#x2a21de>
#<excl::bidirectional-terminal-stream @ #x2d59c6>
#<Function top-level-read-eval-print-loop @
...)1] user(10): (defun bar (x) (car (list x)))
[;; BAR is now just the identity function, but it will work
bar1] user(11): :error
[the car of 10 which is not a cons.
Attempt to take 1] user(12): :current
[10)
(func 1] user(13): :restart
[100
user(14): (defun fact (n)
cond ((= 1 n) 1) (t (* n
(1- n))))))
(fact (
factuser(15): (fact nil)
nil is an illegal argument to =
Error: condition type: type-error]
[1] user(16): :zo
[
Evaluation stack:
error type-error :datum ...)
(= 1 nil)
->(cond (# 1) (t #))
(nil)
(fact eval (fact nil))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level#x2a21de>
#<excl::bidirectional-terminal-stream @ #x2d59c6>
#<Function top-level-read-eval-print-loop @
...)1] user(17): :pop
[user(18):
cl:*modules*
list. The debugger will not load the disassembler if it is not present. It will print a message at the end of the backtrace saying that ghost frames are not displayed because the disassembler is not loaded. The disassembler can be loaded by calling the function disassemble function or by evaluating (require :disasm)
. It is not an error to evaluate that form when the disassembler is already loaded....
), the debugger has determined that additional frames have been optimized off the stack by tail merging but that the functions called in these missing frames cannot be determined.In the following backtrace, the frame [... foo]
is a ghost frame. It indicates that start-fun called foo and that foo did not itself call list-length, so foo must have called something else before a call to list-length:
error type-error :datum ...)
(list-length 1)
->(
[... foo]1)
(start-fun eval (start-fun 1)) (
The example that generated this backtrace is given in What kinds of optimizations cause ghost frames? below. From the example, we know that start-fun called foo, which called bar, which called baz, which called list-length, and looking at the backtrace, we know that calls to foo, bar, and baz have been optimized out, but the system can still detect that foo must have been called.
The remainder of this section contains the following headings:
...
) mean in a ghost frame?Typically, Lisp executes a series of function calls. It maintains a stack of function call frames that show the sequence of functions, each calling the next, that resulted in the current state of Lisp. However, compiled code may cause the frames associated with functions actually called to be removed from the stack. Doing so makes code run faster but means that a backtrace (displayed by the top-level commands :zoom and :bt) does not show calls to some functions actually called, thus making debugging more difficult.
The debugger will where possible insert frames into a backtrace to indicate either that a specific function was called but optimization has removed the associated frame from the stack or that some function was called and removed from the stack by optimization although the debugger cannot determine exactly which function.
These inserted frames are called ghost frames. They are indicated in moderate or verbose printed backtraces by being surrounded with brackets ([]). Consider the following backtraces. The first shows the interpreted code. The second shows compiled code at maximum optimization. The [... foo]
frame in the second backtrace is a ghost frame, indicating that there was a call to foo whose associated frame is no longer on the stack. The suspension points (...
) further indicate that other functions were called whose associated frames have been removed from the stack, although the debugger cannot determine which ones (we of course know from the interpreted backtrace that the missing functions are bar and baz):
; Interpreted code backtrace:
error type-error :datum ...)
(list-length 1)
->(1)
(baz 1)
(bar 1)
(foo 1)
(start-fun eval (start-fun 1))
(
; Compiled code backtrace:
error type-error :datum ...)
(list-length 1)
->(
[... foo]1)
(start-fun eval (start-fun 1)) (
Frames get removed from the stack by a compiler optimization called non-self tail merging. Consider the following example (which generated the backtraces shown above when compiled with speed 2, debug 1):
defun start-fun (x) (foo x) nil)
(defun foo (x) (print x) (bar x))
(defun bar (x) (princ x) (baz x))
(defun baz (x) (pprint x) (list-length x)) (
The error for which the backtrace is displayed above occurs when the form (start-fun 1)
is evaluated. 1 is not a legal argument to list-length (which expects a list) and so an error is signaled.
This is a pretty trivial example but it is not uncharacteristic of real code. foo, bar, and baz each accepts an argument, does something with the argument, and then passes the argument to the next function. foo, bar, and baz each has the property that once the system has reached the last form, nothing further is required of the function.
Thus, foo calls print and then calls bar. foo is going to return whatever bar returns and there are no possible side effects that are not caused by bar. At the point where foo calls bar, there is no reason for a frame associated with foo to be on the stack, except for debugging. Indeed, keeping the frame on the stack causes the stack to grow more than is absolutely necessary and, when bar returns, causes extra instructions (to exit from foo) to be executed. Things would run faster if the frame associated with foo were simply removed from the stack and the system acted as if start-fun had called bar directly (the print called for in foo has already been completed by the time bar is called).
The compiler will optimize this code to remove frames as described as long as the compiler optimization qualities safety, space, speed, and debug have values such that the switch tail-call-non-self-merge-switch is either a function returning true or is itself true. In that case, the compiler will generate code which will make the stack look as if the functions were defined as follows:
defun start-fun (x)
(
(foo x)
(bar x)
(baz x)list-length x))
(defun foo (x) (print x))
(defun bar (x) (princ x))
(defun baz (x) (pprint x)) (
This code will run faster and suppress unnecessary stack growth but the stack will not reflect the actual sequence of function calls. Thus, when start-fun is called with the argument 1, list-length will signal an error because its argument is not a list and the backtraces shown above will result.
Compiler optimization switches in general and tail-call-non-self-merge-switch in particular are described in compiling.html.
Since all references to foo, bar, and baz in our example have disappeared from the stack by the time Lisp enters a break loop (when list-length signals an error), the information on the stack is insufficient to print ghost frames. One piece of information, is available, however: the location to which control should return in start-fun when list-length has completed. The debugger can access that return location and can (assuming the disassembler has been loaded) disassemble the code for the function named by start-fun. Looking at that disassembly, the debugger can determine that the return location is just after a call to foo.
Therefore, it knows that foo was called but the foo frame has been optimized off the stack. From that information, the debugger generates the foo ghost frame.
The debugger also knows that some function must have called list-length (since the list-length frame is still on the stack). Examining the disassembly of foo shows that foo could not have called list-length itself and so the debugger also knows that at least one other function must have been called between foo and list-length (we know that bar and baz were called). Therefore, the debugger puts the suspension points in the ghost frame to indicate the missing ghost frames ([... foo]). However, the debugger is not able to tell which function called by foo called list-length.
In order for the debugger to determine that frames have been optimized off the stack, it must have access to the disassembler. The disassembler in Allegro CL is not included as part of the base system. Instead, it is loaded when needed, typically when the function disassemble is called.
When the disassembler is loaded, the system adds the string "disasm" to the list that is the value of *modules*. The debugger will not load the disassembler just to print ghost frames. So, when the disassembler is loaded, the debugger will print ghost frames in backtraces. When the disassembler is not loaded, the debugger will not print such frames. In that case, the following message will be printed at the end of the backtrace:
the disassembler must be loaded) (to see any ghost frames,
The short answer is no. Various debugger commands in Allegro CL operate on the current frame. These include :restart, :return, :current, :local, etc. Since ghost frames are not really on the stack, applying these commands to a ghost frame does not make sense. In fact, it is not possible to make a ghost frame the current frame. Here is the backtrace shown above:
error type-error :datum ...)
(list-length 1)
->(
[... foo]1)
(start-fun eval (start-fun 1)) (
The arrow (->) indicates the current frame is the list-length frame. If you call the command :dn (which moves the current frame down one frame on the stack), the display changes as follows:
error type-error :datum ...)
(list-length 1)
(
[... foo]1)
->(start-fun eval (start-fun 1)) (
That is, the ghost frame is skipped and the next real frame, the start-fun frame, becomes the current frame.
The backtrace shown just above indicates the ghost frame [... foo]
. The suspension points indicate that other functions were called between foo and list-length, although the debugger does not know what these functions are.
As described under the heading How does the debugger know about ghost frames? above, the debugger can sometimes tell that a ghost frame belongs in the stack and that the function associated with the ghost frame cannot have called the function associated with the next real frame on the stack. Thus, in our example, the debugger can tell that a foo frame must have been on the stack (so it adds a ghost frame) and can further tell foo did not call list-length (and so adds suspension points to the ghost frame).
We have already said that suspension points (...
) indicate that other functions were called between the ghost frame and the next real frame. However, if there are no suspension points, you cannot immediately conclude that no other functions were called.
Consider the following modification of our example above. Instead of defining foo this way:
defun foo (x) (print x) (bar x)) (
we define it as follows:
defun foo (x) (let ((y (list-length (list x)))) (print y)) (bar x)) (
(Again, a trivial example since y is obviously 1.) We compile foo again and again evaluate (start-fun 1)
. Again, we get an error (from the call to list-length in baz) and the following backtrace is displayed:
error type-error :datum ...)
(list-length 1)
->(
[foo]1)
(start-fun eval (start-fun 1)) (
Here, the ghost frame for foo has no suspension points even though we know that foo called bar called baz called list-length, where the error occurred. However, because foo itself calls list-length (for a different purpose), the debugger could not conclude that foo did not call list-length with an argument which resulted in an error.
Therefore, suspension points indicate missing ghost frames for sure. No suspension points does not by itself mean there are no missing ghost frames.
First, some functions are hidden by the system from backtraces to avoid unnecessary clutter. These functions are printed when :zoom is called with arguments :all t
.
However, ghost frames may also be missing in cases similar to our example. Suppose start-fun, which was originally defined (in The ghost frame has no ...
s; are all possible frames displayed?) above:
defun start-fun (x) (foo x) nil) (
was instead defined as follows:
defun start-fun (x) (funcall 'foo x) nil) (
Again, we evaluate (start-fun 1)
and do a :zoom. The following backtrace is printed:
error type-error :datum ...)
(list-length 1)
->(1)
(start-fun eval (start-fun 1)) (
The moral of this and the information under the previous heading, therefore, is that printed information is always guaranteed but missing information should not be depended upon to accurately reflect the situation.
Calling :zoom with arguments :brief t
or calling the :bt command prints an abbreviated backtrace. Here is roughly what is printed when :bt is called after evaluating (start-fun 1)
. The original definitions of foo and start-fun are in What kinds of optimizations cause ghost frames? above. Note that you may see additional frames.
list-length <-
eval <- tpl:top-level-read-eval-print-loop <-
[... foo] <- start-fun <- tpl:start-interactive-top-level
(This backtrace includes frames for functions below start-fun in the stack (such as eval) that were left out in the backtraces printed above.)
If the disassembler is not loade and so "disasm" is not on the cl:*modules*
list, ghost frames will not be printed. However, if that string is on *modules*, ghost frames will be printed. The debugger will always print ghost frames if it can. Load the disassembler with
require :disasm) (
There is no way to unload the disassembler. It is typically already loaded at startup in development images. (See the discussion of the include-devel-env keyword argument to build-lisp-image in Arguments to build-lisp-image 1 in building-images.html.)
Yes, they can. Many system (i.e. predefined) functions in Allegro CL are compiled at a settings of speed and debug which cause tail-call-non-self-merge-switch to be true (or if the switch itself has a non-functionp true value. Therefore, it is possible that ghost frames can appear identified with symbols naming system functions.
The tracer provides a way to track or trace when functions are called. For example, when tracing a function, a message is printed upon entering and exiting the function.
The tracer is invoked at the top level using :trace and turned off using :untrace. The tracer can also be invoked and exited using the macros trace and untrace, which have the same argument syntax as their top-level command counterparts.
The output from :trace is designed to be readable - a function being traced may be called many times, and the entrance and exit from each instance should be obvious, by the numbers at the beginning of the lines and the indentation of the lines printed by the traced function. Also printed at the beginning is a number in brackets, such as [1]. This number indicates the process and can be associated with an actual process by looking at the information printed by the :processes top-level command, in the Bix (Bindstack Index) column.
:normal
, :maximized
, or :icon
. Output will go to the Debug window if the Trace dialog does not exist or has state :shrunk
. Click on the close button of the Trace dialog if you want output to go to the Debug window.:trace prints function names with a package qualifier for functions on symbols not accessible in the current package. The full package name will be printed unless the variable *print-nickname* is t
, in which case the package nickname is printed.
The following are valid options to :trace:
Option | Arguments | Description |
:condition |
expr | Trace this function if expr evaluates to a true value. |
:break-before
|
val |
The expression val is evaluated just before entering a
function. If val evalates to a non-nil
value, then a new break level is entered. Otherwise, execution
continues. When used in combination with :inside and :not-inside,
breaks only occur when the :inside and :not-inside conditions are
satisfied.
|
:break-after |
val | The expression val is
evaluated just after exiting a
function. If val is t , then a new
break level is entered. Otherwise, execution
continues. When used in combination with :inside
and :not-inside, breaks only occur when the :inside
and :not-inside conditions are satisfied. |
:break-all |
val | The expression val is
evaluated just before entering a
function and just after exiting a function. If val
is t , then a new break level is
entered. Otherwise, execution continues. When used in
combination with :inside
and :not-inside, breaks only occur when the :inside
and :not-inside conditions are satisfied. |
:inside
|
func |
Trace this function if Lisp is currently inside a call of the
function func.
func may also be a list of functions, optionally starting with
the symbols cl:and or cl:or. If neither symbol or
cl:and starts the list (e.g.
For example, |
:not-inside
|
func |
Trace this function if Lisp is not currently inside the
evaluation of the function func.
func may also be a list of functions, optionally starting with
the symbols cl:and or cl:or. If neither symbol or
cl:and starts the list (e.g.
For example, |
:print-before |
expr | expr should either be a single object or a list of objects which will be evaluated. The results will be printed before entering the function. |
:print-after |
expr | expr should either be a single object or a list of objects which will be evaluated. The results will be printed after leaving the function. |
:print-all |
expr | expr should either be a single object or a list of objects which will be evaluated. The results will be printed before entering and after leaving the function. |
:show-stack |
n | Causes stack frames (as if approximately printed by
:zoom with :moderate t and
:function nil
as arguments) to be shown just before the
function being traced is entered. n should a positive
integer, t ,
or nil . If a positive integer, that many
stack frames will be shown. n can also
be t , which requests all frames on the stack
to be printed; or nil , which turns off
showing the stack. See the example below. |
A call to function foo is inside a call to function bar if bar appears on the stack when foo is called. bar can call foo directly, meaning there is an explicit call to foo in the code defining bar, or indirectly, meaning bar calls another function which (perhaps calls more itermediate functions) which calls foo directly.
There are a few special cases. One is caused by tail-merging. Thus, if bar calls baz which does a tail-merged call to foo, then foo is considered inside bar but not inside baz.
The other special case is generic functions. If a method is on the stack, its generic function is also considered to be on the stack, even though it never will be seen there (meaning you can specify the generic function rather than the specific method). Thus the following all work for tracing foo inside a call to device-read called on a terminal-simple-stream
, but the first will catch calls to foo inside any device-read method.
:trace (foo :inside device-read)method device-read (terminal-simple-stream t t t t)))
:trace (foo :inside #'(method device-read (terminal-simple-stream t t t t)))) :trace (foo :inside ((
Tracing of individual objects or all tracing can be stopped with the :untrace command.
The value of *trace-output* is the where :trace sends output, which is normally *terminal-io*. *print-level*, *print-length*, *print-array*, and *print-circle* are bound to *trace-print-level*, *trace-print-length*, *trace-print-array*, and *trace-print-circle* during trace output. *print-long-string-length* is bound to the value of *trace-print-long-string-length* during trace output.
1): :tr (car :show-stack 10)
cl-user(car)
(2): (car '(a b))
cl-user(0[2]: (car (a b))
0^ <- (system::..runtime-operation (a b))
0^ <- (eval (car '(a b)))
0^ <- (top-level::read-eval-print-one-command nil nil)
0^ <- (excl::read-eval-print-loop :level 0)
0^ <- (top-level::top-level-read-eval-print-loop1)
0^ <- (top-level:top-level-read-eval-print-loop)
0^ <- ((:runsys system::lisp_apply))
0^ <- (top-level:start-interactive-top-level
0/1 @
#<terminal-simple-stream [initial terminal io] fd #x4029b84a>
nil)
#<Function top-level-read-eval-print-loop> 0^ <- (excl::start-lisp-execution t)
0[2]: returned a
a3): cl-user(
trace and :trace may be passed function names but not function objects. Allegro CL allows specifying function objects to be traced using the ftrace and funtrace functions.
Each function takes a function specification as an argument. The function specification can be a function name (as with trace and untrace) or a function object (which is not accepted by trace and untrace).
untrace and :untrace called with no arguments stop all tracing, including tracing started by ftrace with a function object as an argument, but you should use funtrace to stop tracing of a function object if you want some tracing to continue.
Here is an example:
1): (defun foo () (break "hello"))
cl-user(
foo2): (foo)
cl-user(
Break: hello
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this process.
3): :zo
[1c] cl-user(
Evaluation stack:
break "hello")
(
->(foo)eval (foo))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level0/1 @
#<terminal-simple-stream [initial terminal io] fd #x7115d9da>
#<Function top-level-read-eval-print-loop> ...)4): :func
[1c] cl-user(
#<Interpreted Function foo>5): (ftrace *)
[1c] cl-user(
#<Interpreted Function foo>6): :prt
[1c] cl-user(7): (foo) ;; :prt evaluation
cl-user(0: (foo)
Break: hello
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this process.
8): :cont
[1c] cl-user(0: returned nil
nil
9): (funtrace #'foo)
cl-user(
#<Interpreted Function foo>10): cl-user(
In the following example, we trace the factorial function and then give an example of tracing one function inside another.
;; Note: this manual is generic over several implementations.
;; The stack may differ from one implementation to another
;; so the output you see may differ in detail from what is
;; reproduced here.
38): (defun fact (n)
cl-user(cond ((= n 1) 1)
(t (* n (fact (1- n))))))
(
fact39): (fact 4)
cl-user(24
40): :trace fact
cl-user(
(fact)41): (fact 4)
cl-user(0[1]: (fact 4)
1[1]: (fact 3)
2[1]: (fact 2)
3[1]: (fact 1)
3[1]: returned 1
2[1]: returned 2
1[1]: returned 6
0[1]: returned 24
24
42): (defun deep (x) (deeper (list x)))
cl-user(
deep43): (defun deeper (x) (format t "~&~s~%" x))
cl-user(
deeper44): (deep 10)
cl-user(10)
(nil
45): :tr (deeper :inside deep)
cl-user(
(deeper)46): (deeper 10)
cl-user(10
nil
47): (deep 10)
cl-user(0[1]: (deeper (10))
10)
(0[1]: returned nil
nil
48): :tr (deeper :break-before t)
cl-user(
(deeper)49): (deep 10)
cl-user(0[1]: (deeper (10))
Break: traced call to deeper
Restart actions (select using :continue):0: return from break.
1: Return to Top Level (an "abort" restart).
2: Abort entirely from this process.
50): :zo
[1c] cl-user(
Evaluation stack:
break "traced call to ~s" deeper)
(10)
->(deep eval (deep 10))
(
(tpl:top-level-read-eval-print-loop)
(tpl:start-interactive-top-level0/1 @ #x4095faa>
#<terminal-simple-stream [initial terminal io] fd
#<Function top-level-read-eval-print-loop> ...)51): :cont
[1c] cl-user(10)
(0[1]: returned nil
nil
;; The [1] that appears in the trace output identified the process
;; being traced. It can be associated with an actual process
;; by looking at the output of the :processes top-level
;; command (nicknamed :proc), under the Bix (bindstack index)
;; column. We see from the output it is the Initial
;; Lisp Listener.
52): :proc
cl-user(
P Bix Dis Sec dSec Priority State Process Name, Whostate, Arrest* 1 16 279 4.5 0 runnable Initial Lisp Listener
* 3 0 0 0.0 0 waiting Connect to Emacs daemon,
waiting for input* 4 0 0 0.0 0 inactive Run Bar Process
* 6 0 2 0.0 0 waiting Editor Server, waiting for input
53):
cl-user(
;; In the next example, we define functions that call other
;; functions after printing messages about what they are calling
;; what called them. This illustrates the :inside and :not-inside
;; options.
53): (defun foo ()
cl-user(progn (format t "foo calling bar~%") (bar 'foo))
(progn (format t "foo calling baz~%") (baz 'foo))
(progn (format t "foo calling bzz~%") (bzz 'foo)))
(
foo54): (defun bar (from)
cl-user(progn (format t "bar calling baz from ~S~%" from)
(
(baz 'bar))progn (format t "bar calling bzz from ~S~%" from)
(
(bzz 'bar)))
bar55): (defun baz (from)
cl-user(progn (format t "baz calling bzz from ~S~%" from)
(
(bzz 'baz)))
baz56): (defun bzz (from)
cl-user(format t "bzz called from ~S~%" from))
(
bzz
;; Here is an untraced call to FOO:
57): (foo)
cl-user(
foo calling bar
bar calling baz from foo
baz calling bzz from bar
bzz called from baz
bar calling bzz from foo
bzz called from bar
foo calling baz
baz calling bzz from foo
bzz called from baz
foo calling bzz
bzz called from foonil
;; Here we trace BZZ when called inside both FOO and BAZ (:inside
;; followed by a list contains an implicit AND, so the call
;; must be inside all listed functions):
58): :trace (bzz :inside (foo baz))
cl-user(
(bzz)59): (foo)
cl-user(
foo calling bar
bar calling baz from foo
baz calling bzz from bar0[1]: (bzz baz)
bzz called from baz0[1]: returned nil
bar calling bzz from foo
bzz called from bar
foo calling baz
baz calling bzz from foo0[1]: (bzz baz)
bzz called from baz0[1]: returned nil
foo calling bzz
bzz called from foo;; this call to BZZ is not traced since
;; it is not inside BAZ.
nil
60): :untrace ;; We remove all tracing
cl-user(nil
;; Now tracing will happen when inside FOO but not inside BAR:
61): :trace (bzz :inside foo :not-inside bar)
cl-user(
(bzz)62): (foo)
cl-user(
foo calling bar
bar calling baz from foo
baz calling bzz from bar;; inside BAR so not traced.
bzz called from baz
bar calling bzz from foo
bzz called from bar
foo calling baz;; not inside BAR
baz calling bzz from foo 0[1]: (bzz baz)
bzz called from baz0[1]: returned nil
foo calling bzz0[1]: (bzz foo)
;; Again, not inside BAR
bzz called from foo 0[1]: returned nil
nil
63): cl-user(
Tracting compiled code
Note that the factorial function is recursive. When traced interpreted, all the calls made during the evaluation are shown. But the behavior when compiled may be different since on some architectures, self-calls by recursive functions are compiled in such a way that they avoid going through the normal function calling sequence (which is not necessary on such platforms, and so unnecessary instructions can be avoided). Because since the tracer catches function calls from the normal function calling sequence, it will not see most of the recursive self calls and it will look like the function was called only once. See the Why doesn't tracing a self-calling function trace the inner calls? FAQ iten in the Allegro CL FAQ for more information. Compilation may also affect trace output in other ways. You always get maximum information by tracing interpreted code.
CLOS methods can be traced by name. Here is an example.
user(16): (defmethod my-function ((x integer))
cons x :integer))
(integer)>
#<standard-method my-function (user(17): (my-function 1)
1 . :integer)
(user(18): (trace ((method my-function (integer))))
method my-function (integer)))
(user(19): (my-function 1)
0: ((method my-function (integer)) 1)
0: returned (1 . :integer)
1 . :integer)
(user(20): (untrace (method my-function (integer)))
method my-function (integer)))
((user(21): (my-function 1)
1 . :integer)
(user(22):
Methods and internal functions are typically named by function specs (see Function specs (fspecs) in implementation.html). This section describes how to trace such things, concetrating on setf, :before and :after methods and internal functions (such as those defined by flet or labels).
Here is how to trace setf, :before and :after methods. Note that the methods must be defined before they can be traced (we have not done that -- the examples simply show the format of the calls to trace and untrace):
trace ((method (setf slot-1) (t baz))))
(trace ((method foo :before (integer))))
(trace ((method foo :after (integer)))) (
The extra set of parentheses is required to avoid confusion with specifying trace options (they are specified with a list whose car is the function to be traced and whose cdr is a possibly empty list of options). Note that the extra set of parentheses is not used with untrace:
untrace (method (setf slot-1) (t baz)))
(untrace (method foo :before (integer)))
(untrace (method foo :after (integer))) (
A generic function itself can be traced exactly like any other function.
Here is an example tracing an internal function, defined in a labels or flet. Note that the form containing the call to labels or flet must be compiled.
1): (defun myfunc (a b c)
cl-user(labels ((cubit (arg) (expt arg 3)))
(if (> a b)
(+ (cubit a ) (cubit c))
(+ (cubit b) (cubit c)))))
(
myfunc2): (compile 'myfunc)
cl-user(
myfuncnil
nil
4): (trace ((labels myfunc cubit)))
cl-user(labels myfunc cubit))
((5): (myfunc 1 2 3)
cl-user(0: ((labels myfunc cubit) 2)
0: returned 8
0: ((labels myfunc cubit) 3)
0: returned 27
35
6): cl-user(
Here is another example. Again, the form must be compiled.
;;; File foo.cl:
in-package :cl-user)
(defclass thing ()
(1 :initarg :s1 :accessor s1)
((s1 :initform 2 :initarg :s2 :accessor s2)
(s2 :initform 3 :initarg :s3 :accessor s3)))
(s3 :initform
defmethod doit ((arg thing))
(labels ((cubit (arg) (expt arg 3)))
(if (> (s1 arg) (s2 arg))
(+ (cubit (s1 arg) ) (cubit (s3 arg)))
(+ (cubit (s2 arg)) (cubit (s2 arg))))))
(
;;; End of file foo.cl:
1): :ld foo.cl
cl-user(; loading /home/user1/foo.cl
2): (trace ((labels (method doit (thing)) cubit)))
cl-user(labels (method doit (thing)) cubit)' is not fbound
Error: `(condition type: undefined-function]
[
restart actions (select using :continue):
0: return to Top Level (an "abort" restart).
1: Abort entirely from this process.
;;; The form must be compiled so that internal functions
;;; can be traced.
1] CL-USER(3): :res
[4) :cload foo.cl
cl-user(;;; Compiling file foo.cl
;;; Writing fasl file foo.fasl
;;; Fasl write complete
5): (trace ((labels (method doit (thing)) cubit)))
cl-user(labels (method doit (thing)) cubit))
((6): (setf thing1 (make-instance 'thing))
cl-user(#x7150fac2>
#<thing @ 7): (doit thing1)
cl-user(0: ((labels (method doit (thing)) cubit) 2)
0: returned 8
0: ((labels (method doit (thing)) cubit) 2)
0: returned 8
16
8): cl-user(
The memlog module is a trace facility which stores data in an internal data structure rather than printing it. This can be useful in any Lisp but is particularly useful in an SMP lisp as it mitigates the problem of coordinating output and the problem of affecting operation because of the I/O cost. It is documented in smp.html in the section Memlog: A Facility for Minimally Intrusive Monitoring of Complex Application Behavior.
The stepper described in this section is triggered by the step macro and associated tools. There is also a separate, modern stepper, the Lisp DeBug (LDB) stepper, which is described below in two sections. The first section, The Lisp DeBug (ldb) stepper describes the LDB stepper itself, and the second section, The source stepper describes its source stepping variant. The LDB stepper is not related to the original stepper described in this section and its subsections. We recommend that users consider using that more modern tool before using the original stepper.
The stepper allows the user to watch and control the evaluation of Lisp expressions, either inside certain functions or over certain expressions. When stepping is turned on, evaluation of all expressions is done in single-step mode - after evaluating one form, a step read-eval-print loop is entered, from which the user may continue or abort.
It is only useful to step through compiled code. You cannot effectively step through interpreted code. (Doing so results in stepping through the interpreter itself, with hundreds of uninteresting steps). Further, see verify-funcalls-switch. funcall'ed functions in certain cases in compiled code will not be caught by the stepper if the call was compiled with that compiler switch false.
If you ever see the entry
> <excl::interpreted-funcall ...
immediately enter the command :sover.
If you instead take another step (by hitting Return, for example), get out of stepping by entering
step] cl-user(20): :step nil
[step] cl-user(21): :sover [
and then you can start stepping afresh.
The :step top-level command is similar to the standard Common Lisp macro step.
With no arguments or an argument of nil
, :step turns off stepping. With an argument of t
, stepping is turned on globally. Otherwise the arguments must be symbols naming functions, and stepping is done only when inside one of the functions given to :step.
Once stepping is turned on, the top level recognizes three more commands: :scont, :sover, and carriage return (which is a synonym for :scont 1). Also, the top-level prompt for read-eval-print loops when stepping is enabled is prefixed with [step], as a reminder that the above step commands are available.
Entering :step nil
turns off stepping. You may have to then enter :sover
to end the current stepping through a function. Turning off stepping is very useful when you accidently start stepping through an interpreted function.
user(45): (defun fact (n) (if (= n 1) 1 (* n (fact (1- n)))))
factuser(46): :step fact
user(47): (fact 3)
1: (fact 3)
step] user(48):
[2: (excl::interpreted-funcall #<Interpreted Function fact> 1 32864546 0)
;; :sover here would get you out
step] user(48):
[3: (excl::extract-special-decls ((block fact (if # 1 #))))
;; it is now too late. Turn stepping off.
step] user(48): :step nil
[step] user(49): :sover
[3: nil ((block fact (if (= n 1) 1 (* n #))))
result 2: 6
result 1: 6
result 6
user(50):
Command | Arguments | Description |
:scont | n | Continue stepping, for n expressions, beginning with the evaluation of the last expression printed by the stepper. If n is not provided, it defaults to 1. |
:sover | Evaluate the current expression in normal, non-stepping mode. |
*print-level* and *print-length* are bound to *step-print-level* and *step-print-length* during stepper output. *print-long-string-length* is bound to the value of *step-print-long-string-length* during stepper output.
The example below demonstrates the use of the stepper. In it, we define fact, the factorial function, and then step through it. We use carriage returns for each single step. Notice particularly the difference between stepping through fact when it runs interpreted and when it runs compiled.
;; Note: this manual is generic over several implementations.
;; The stack may differ from one implementation to another
;; so the output you see may differ in detail from what is
;; reproduced here.
user(3): (defun fact (n)
if (= n 1) 1 (* n (fact (1- n)))))
(
factuser(4): (fact 3)
6
user(5): (compile 'fact)
factuser(6): :step nil
;; we turn all stepping off
user(7): (step fact)
;; Here we use the function form of step.
;; It has the same effect as :step fact.
user(8): (fact 3)
1: (fact 3)
step] user(9):
[2: (excl::*_2op 2 1)
step] user(9):
[2: 2
result 2: (excl::*_2op 3 2)
step] user(8):
[2: 6
result 1: 6
result 6
user(9):
The ldb stepping facility allows the user to place breakpoints at various instruction-level locations. Note that the ldb-stepping functionality may not be included in the running image. Evaluate (require :lldb)
to ensure that the functionality is loaded before trying to use the facility.
Note there are two steppers in Allegro CL, the original stepper (associated with the step macro and described in the section The original stepper above), and the LDB stepper described in this section, which has a source stepper variant described in the section The source stepper below. We recommend that users start with the LDB stepper and the source variant, which are more powerful that the original stepper.
The ldb stepper utilizes several top-level commands to create the stepping environment.
They are:
:ldb | Controls the installing (enabling) and uninstalling (disabling) of breakpoints, and the establishment of ldb mode. |
:break | Allows the user to add or delete breakpoints. |
:step | When in ldb-step mode, the step command recognizes several sub-commands as its first argument. See the full description for more information. |
:local | When a breakpoint is hit, the default current frame is the register context in which the breakpoint occurred (and thus ldb-step mode was entered). See the full description for more information. |
:register | At a breakpoint, print the saved context (ie its registers) for the current frame. See the full description for more information. |
[return] |
When in ldb-step mode,
causes the previous step subcommand to be re-executed. If there was no previous step
subcommand, a :step over is assumed. |
A Lisp breakpoint is a stopping point for execution of Lisp functionality. It is represented by a structure which carries information, including the function it represents and the relative program counter within that function. It may be installed (causing execution of the function to be diverted to the breakpoint facility) or uninstalled (causing the breakpoint to have no effect on executuon of the function).
Every breakpoint designates a language. The two languages that are currently supported are :asm
(which steps instruction-by-instruction) and :lisp
(which steps form-by-form. Other languages can be added, and as a proof-of-concept the :python
language was added by Willem Broekema and can be loaded from cl-python. This language front-end steps line-by-line.
The :asm
language personality uses the basic breakpoint
structure, and other languages can include this structure in their own larger structures. The breakpoint
structure includes slots common to all breakpoints, which include function
and pc
values which identify the breakpoint, and also next-pc
, branch-pc
, branch-type
, and other internal and external slots which describe or maintain the state of the breakpoint.
A Lisp breakpoint is also called a Lisp source record. It includes the breakpoint struct and has all of its slots, and in addition it has several slots which describe whatever expression the Lisp breakpoint is representing. Unlike the asm breakpoint, whose function and pc slot uniquely identify the instruction it replaces, multiple Lisp breakpoints can have the same function and pc designation, yet can remain distinct. Source record structs are built by the Lisp compiler and is made available in the fasl file via the save-source-level-debug-info-switch and is loaded when *load-source-file-info* is true.
When possible, a Lisp breakpoint will contain position information about its source location. As with the pc location, position information isn't always unique (e.g. a macro form may be expanded in place to another form, and each of those forms will have its own record but will have the same start and end character positions).
Because Lisp breakpoints have unique record numbers, they may be selected absolutely via their record number, which is an index into the array of Lisp records in the function's source record vector. But it is also possible to select a Lisp breakpoint using either the pc value or the start position, although it is not possible to map either characteristic to a particular record; the selection process is done algorithmically according to the need. With the :break top-level-command, the :rec
option will select the precise breakpoint by record, but the default :pc
option will tend to select the record that is the closest to the source form, and the :pos
option will select the outermost record in a macro-expansion group of forms.
Because of the slight ambiguity of selecting Lisp breakpoints, sliding was invented. This is a mechanism for navigating the source record set with accuracy. It also allows position information to be conveyed to the user even when not currently stepping through breakpoints. The Lisp has a single slide point, which is a cursor of sorts which when non-nil
holds a Lisp source record. It is usually transient, and gets set whenever:
the :break top-level command is used to set a breakpoint,
the :break top-level command is used with the :slide option to only slide to that breakpoint,
a breakpoint is encountered during execution,
the :zoom command shows the current frame and the current function has source debug info available, or
the :slide top-level command is used to move the slide point.
If :zoom output shows "slide point set to ..." just after the current frame, typing :slide point or :slide here will display the current slide point, which may have position information in it. See the :zoom example for an example.
Before starting ldb-mode stepping, be sure that the functionality is loaded by evaluating:
require :lldb) (
Entering ldb-mode can then be done in one of two ways:
Use the :ldb t
command to start debugging
Use the with-breakpoints-installed macro around a form.
Either method allows breakpoints to be hit by installing them. If the :break command is used after the :ldb command, any new breakpoints will be properly installed.
Once a break is hit, a message is printed out, and ldb-step mode is entered. In this mode, breakpoints are not installed, but the top-level is primed to do fast installation/deinstallation/stepping of the breakpoints with little keyboard input.
The ldb stepper provides the following functional interface.
add-breakpoint | Adds a breakpoint. |
delete-breakpoint | Deletes an existing breakpoint. |
with-breakpoints-installed | Evaluates a body with breakpoints installed during the evaluation. |
This example run is on a linux x86 version. The output on different platforms will be different. Comments are added within ;[ ]:
1): (require :lldb)
cl-user(
Fast loading lldb.fasl2): :br count 17 ;[add a breakpoint (but do not install)]
cl-user(17
Adding #<Function count>: 17: 89 45 dc movl [ebp-36],eax ; item
3): (count #\a "abacad")
cl-user(3 ;[no breakpoints had been installed]
4): :ldb t ;[start things out]
cl-user(ldb] cl-user(4): (count #\a "abacad")
[= #<Function count>, pc=17
Hit breakpoint at func 17: 89 45 dc movl [ebp-36],eax ; item
breakpoint-> 5):
[ldb-step] cl-user(= #<Function count>, pc=20
Hit breakpoint at func 20: 89 55 cc movl [ebp-52],edx ; sequence
temp brkpt-> 6): :br ;[ask to show current breakpoints]
[ldb-step] cl-user(17 (20 :temp))
#<Function count>: (7): :loc :%eax ;[eax is the first arg on x86]
[ldb-step] cl-user(#\a
8): :loc :%edx ;[edx is the second arg on x86]
[ldb-step] cl-user("abacad"
9): :step into ;[this will have no effect until later]
[ldb-step] cl-user(= #<Function count>, pc=23
Hit breakpoint at func 23: 8d 49 fe leal ecx,[ecx-2]
temp brkpt-> 9):
[ldb-step] cl-user(= #<Function count>, pc=26
Hit breakpoint at func 26: 8d 45 10 leal eax,[ebp+16] ; (argument 2)
temp brkpt-> 9):
[ldb-step] cl-user(= #<Function count>, pc=29
Hit breakpoint at func 29: 8d 95 5c ff leal edx,[ebp-164]
temp brkpt->
ff ff 9):
[ldb-step] cl-user(= #<Function count>, pc=35
Hit breakpoint at func 35: 33 db xorl ebx,ebx
temp brkpt-> 9):
[ldb-step] cl-user(= #<Function count>, pc=37
Hit breakpoint at func 37: b3 c8 movb bl,$200
temp brkpt-> 9):
[ldb-step] cl-user(= #<Function count>, pc=39
Hit breakpoint at func 39: ff 57 4f call *[edi+79] ; kwdscan
temp brkpt-> 9): ;[note that primcalls are _not_ stepped into]
[ldb-step] cl-user(= #<Function count>, pc=42
Hit breakpoint at func 42: 8d 9d 5c ff leal ebx,[ebp-164]
temp brkpt->
ff ff 9):
[ldb-step] cl-user(some stepping deleted here]
[= #<Function count>, pc=155
Hit breakpoint at func 155: 3b 7d e0 cmpl edi,[ebp-32] ; end
temp brkpt-> 9):
[ldb-step] cl-user(= #<Function count>, pc=158
Hit breakpoint at func 158: 0f 85 93 00 jnz 311
temp brkpt-> 00 00
9):
[ldb-step] cl-user(= #<Function count>, pc=164
Hit breakpoint at func 164: 8b 45 cc movl eax,[ebp-52] ; sequence
temp brkpt-> 9):
[ldb-step] cl-user(= #<Function count>, pc=167
Hit breakpoint at func 167: 8b 9f 9f fe movl ebx,[edi-353] ; length
temp brkpt->
ff ff 9):
[ldb-step] cl-user(= #<Function count>, pc=173
Hit breakpoint at func 173: b1 01 movb cl,$1
temp brkpt-> 9): :loc :%eax
[ldb-step] cl-user("abacad"
10): :step into
[ldb-step] cl-user(= #<Function count>, pc=175
Hit breakpoint at func 175: ff d7 call *edi
temp brkpt-> 11):
[ldb-step] cl-user(= #<Function length>, pc=0
Hit breakpoint at func 0: 8b c8 movl ecx,eax
temp brkpt-> 11):
[ldb-step] cl-user(= #<Function length>, pc=2
Hit breakpoint at func 2: 80 e1 03 andb cl,$3
temp brkpt-> 11): :br ;[check breakpoints again]
[ldb-step] cl-user(17)
#<Function count>: (2 :temp))
#<Function length>: ((12): :br nil ;[turn off all breakpoints]
[ldb-step] cl-user(13):
[ldb-step] cl-user(3 ;[get right answer]
ldb] cl-user(13): [
Note there are two steppers in Allegro CL, the original stepper (associated with the step macro and described in the section The original stepper above), and the LDB stepper described in part in the section The Lisp DeBug (ldb) stepper above. The source variant of the LDB stepper is described in this section. We recommend that users start with the LDB stepper, which is more powerful that the original stepper.
The source stepper displays source code while stepping through a form. In the IDE, the source stepper is associated with the new Stepper Dialog. It can also be run in terminal mode as described here.
In order for information to be available to the source stepper, files must be compiled while the compiler switch save-source-level-debug-info-switch is either a function returning true or is true itself, which is initially the case whenever the debug optimization quality is 3.
When save-source-level-debug-info-switch is either a function returning true or is itself true, the compiler writes annotation information to the fasl file it produces. It does not produce different code: the same code is produced regardless of the value of save-source-level-debug-info-switch. But when the switch is either a function returning true or is itself true, the annotations added to the fasl file make the fasl file bigger.
The annotated file should be loaded while the variable *load-source-file-info* is true, but the system will prompt for the name of the necessary file if the information is not loaded when the fasl file is loaded. If the annotated file is loaded while *load-source-debug-info* is true, then the source debug information will be also loaded into Lisp at that time. This is not recommended since the information is voluminous and may not be needed, but if you have problems with ensuring that the information is loaded, loading while that variable is true ensures it.
We illustrate the interface with a simple example, showing both styles of tty-backend. The function foo is defined in the file step.cl:
in-package :user)
(
defun foo (file)
(with-open-file (s file :direction :input)
(let ((fm (read s)))
(format t "~S~%" fm)))) (
As the transcript shows, we set the value of the debug optimize quality to 3, then compile step.cl and load step.fasl. We then put a breakpoint at foo and start the ldb debugger. Then we call foo. As we step through, the form being executed is shown. Note that the default step mode is over/0 so function calls are jumped over and we only stop at level 0 source records
The :tty style backend is shown here:
1): (declaim (optimize debug))
cl-user(t
2): (setq *load-local-names-info* t *load-source-file-info* t *load-source-debug-info* t)
cl-user(t
3): (setq *record-source-file-info* t)
cl-user(t
4): :cf step
cl-user(;;; Compiling file step.cl
;;; Writing fasl file /tmp/cfta225001426171
;;; Moving fasl file /tmp/cfta225001426171 to step.fasl
;;; Fasl write complete
5): :ld step
cl-user(; Fast loading /net/gemini/home/duane/step.fasl
6): :br :backend :tty foo
cl-user(0
Adding #<Function foo>: defun foo (file)
Form=(with-open-file (s file :direction :input)
(let (#) (format t "~S~%" fm))))
(0 pc=0 rec=0 in #P"step.cl" starting at 20 ending at 138.
level=
Possible slide directions: (into over across)0
Current step/slide type: over/7): :ldb t
cl-user(ldb] cl-user(8): (foo "step.cl")
[
= #<Function foo>, pc=0
Hit breakpoint at func defun foo (file)
Form=(with-open-file (s file :direction :input)
(let (#) (format t "~S~%" fm))))
(0 pc=0 rec=0 in #P"step.cl" starting at 20 ending at 138.
level=
Possible slide directions: (into over across)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=19
Hit breakpoint at func with-open-file (s file :direction :input)
Form=(let ((fm #)) (format t "~S~%" fm)))
(0 pc=19 rec=2 in #P"step.cl" starting at 41 ending at 137.
level=
Possible slide directions: (into out over across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
0
Sliding to same pc at level=
Form=file0 pc=19 rec=5 style=:ref in #P"step.cl" starting at 60 ending at 64.
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=156
Hit breakpoint at func let ((fm (read s))) (format t "~S~%" fm))
Form=(0 pc=156 rec=12 in #P"step.cl" starting at 88 ending at 136.
level=
Possible slide directions: (out over across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
(Stopping early at call-like form before processing arguments):read s)
Form=(0 pc=156 rec=13 style=:early in #P"step.cl" starting at 98 ending at 106.
level=
Possible slide directions: (out over across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
Entering form from contour:
Form=s0 pc=156 rec=14 style=:ref in #P"step.cl" starting at 104 ending at 105.
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=170
Hit breakpoint at func read s)
Form=(0 pc=170 rec=15 in #P"step.cl" starting at 98 ending at 106.
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=173
Hit breakpoint at func break initializing fm to the result of evaluating (read s)
let ((fm (read s))) ...)
subform: (0 pc=173 rec=16 in #P"step.cl" starting at 94 ending at 107.
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9): :step cont ;; this goes to the next breakpoint
[ldb-step] cl-user(in-package :user)
(nil
ldb] cl-user(10): :ldb nil ;; this stops stepping
[11): cl-user(
The :flip-tty style (which is the default) is shown here:
1): (declaim (optimize debug))
cl-user(t
2): (setq *load-local-names-info* t *load-source-file-info* t *load-source-debug-info* t)
cl-user(t
3): (setq *record-source-file-info* t)
cl-user(t
4): :cf step
cl-user(;;; Compiling file step.cl
;;; Writing fasl file /tmp/cfta244811440231
;;; Moving fasl file /tmp/cfta244811440231 to step.fasl
;;; Fasl write complete
5): :ld step
cl-user(; Fast loading /net/gemini/home/duane/step.fasl
6): :br foo
cl-user(0
Adding #<Function foo>: "step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form0/0 0 2/19 nil/nil nil nil 20 138 (defun foo (file) ...)
-> 1/19 1 2/19 nil/nil nil nil (20) (138) (block foo (with-open-file (s file :direction :input) (let ((fm #)) (format t "~S~%" fm))))
2/19 0-1 4/19 nil/nil nil nil 41 137 (with-open-file (s file :direction :input) (let ((fm (read s))) (format t "~S~%" fm)))
0 pc=0 rec=0 in #P"step.cl" starting at 20 ending at 138.
level=
Possible slide directions: (into over across)0
Current step/slide type: over/7): :ldb t
cl-user(ldb] cl-user(8): (foo "step.cl")
[
= #<Function foo>, pc=0
Hit breakpoint at func "step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form0/0 0 2/19 nil/nil nil nil 20 138 (defun foo (file) ...)
-> 1/19 1 2/19 nil/nil nil nil (20) (138) (block foo (with-open-file (s file :direction :input) (let ((fm #)) (format t "~S~%" fm))))
2/19 0-1 4/19 nil/nil nil nil 41 137 (with-open-file (s file :direction :input) (let ((fm (read s))) (format t "~S~%" fm)))
0
level=
Possible slide directions: (into over across)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=19
Hit breakpoint at func "step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form0/0 0 2/19 nil/nil nil nil 20 138 (defun foo (file) ...)
1/19 1 2/19 nil/nil nil nil (20) (138) (block foo (with-open-file (s file :direction :input) (let ((fm #)) (format t "~S~%" fm))))
2/19 0-1 4/19 nil/nil nil nil 41 137 (with-open-file (s file :direction :input) (let ((fm (read s))) (format t "~S~%" fm)))
-> 3/19 1-2 4/19 nil/nil nil nil (41) (137) (let ((s (open file ...)) (#:with-open-file-abort-160 t)) ...)
4/19 1-2 5/19 nil/nil :early nil nil nil (open file :direction :input)
0
level=
Possible slide directions: (into out over across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
0
Sliding to same pc at level="step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form3/19 1-2 4/19 nil/nil nil nil (41) (137) (let ((s (open file ...)) (#:with-open-file-abort-160 t)) ...)
4/19 1-2 5/19 nil/nil :early nil nil nil (open file :direction :input)
5/19 0-2 6/41 nil/nil :ref nil 60 64 file
-> 6/41 1-2 7/52 nil/nil nil :call nil nil (open file :direction :input)
7/52 1-2 8/52 nil/nil :const nil nil nil (quote t)
0
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=156
Hit breakpoint at func "step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form10/64 1-2 11/156 nil/nil nil nil nil nil (unwind-protect (multiple-value-prog1 (progn (let (#) ...)) ...) ...)
11/156 1-2 12/156 nil/nil nil nil nil nil (multiple-value-prog1 (progn (let ((fm #)) ...)) ...)
12/156 0-2 13/156 nil/nil nil nil 88 136 (let ((fm (read s))) (format t "~S~%" fm))
-> 13/156 0-2 14/156 nil/nil :early nil 98 106 (read s)
14/156 0-2 15/170 nil/nil :ref nil 104 105 s
0
level=
Possible slide directions: (out over across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
(Stopping early at call-like form before processing arguments):"step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form11/156 1-2 12/156 nil/nil nil nil nil nil (multiple-value-prog1 (progn (let ((fm #)) ...)) ...)
12/156 0-2 13/156 nil/nil nil nil 88 136 (let ((fm (read s))) (format t "~S~%" fm))
13/156 0-2 14/156 nil/nil :early nil 98 106 (read s)
-> 14/156 0-2 15/170 nil/nil :ref nil 104 105 s
15/170 0-2 #(16 22)/#(173 391) nil/nil nil :call 98 106 (read s)
0
level=
Possible slide directions: (out over across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
Entering form from contour:"step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form12/156 0-2 13/156 nil/nil nil nil 88 136 (let ((fm (read s))) (format t "~S~%" fm))
13/156 0-2 14/156 nil/nil :early nil 98 106 (read s)
14/156 0-2 15/170 nil/nil :ref nil 104 105 s
-> 15/170 0-2 #(16 22)/#(173 391) nil/nil nil :call 98 106 (read s)
16/173 0-2 17/173 nil/nil :init nil 94 107 ((fm (read s)) fm nil)
0
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=170
Hit breakpoint at func "step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form13/156 0-2 14/156 nil/nil :early nil 98 106 (read s)
14/156 0-2 15/170 nil/nil :ref nil 104 105 s
15/170 0-2 #(16 22)/#(173 391) nil/nil nil :call 98 106 (read s)
-> 16/173 0-2 17/173 nil/nil :init nil 94 107 ((fm (read s)) fm nil)
17/173 0-2 19/173 nil/nil nil nil 115 135 (format t "~S~%" fm)
0
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9):
[ldb-step] cl-user(
= #<Function foo>, pc=173
Hit breakpoint at func "step.cl":
Function: #<Function foo> in #P
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form14/156 0-2 15/170 nil/nil :ref nil 104 105 s
15/170 0-2 #(16 22)/#(173 391) nil/nil nil :call 98 106 (read s)
16/173 0-2 17/173 nil/nil :init nil 94 107 ((fm (read s)) fm nil)
-> 17/173 0-2 19/173 nil/nil nil nil 115 135 (format t "~S~%" fm)
18/173 1-3 19/173 nil/nil :early nil (115) (135) (format t (quote (excl::lazy-formatter-tree . "~S~%")) fm)
0
level=
Possible slide directions: (out across back)0
Current step/slide type: over/9): :step cont ;; this goes to the next breakpoint
[ldb-step] cl-user(in-package :user)
(nil
ldb] cl-user(10): :ldb nil ;; this stops stepping
[11): cl-user(
At each step, you are given some slide options, such as over, into, across, out, and back. Not all options are available at every step. You can use the :slide top-level command with the type of sliding or the :step command at the prompt.
In general, sliding is a conceptual way to navigate around a function, in spite of the fact that there is no mouse to point to select the form you want (with the Stepper Dialog in the IDE, you can use the mouse to indicate what you wish to do). In the tty interface, the :slide command is the mechanism. The :slide command does not alter the state of computation (that is done with actions like a carriage return or :step cont
) but rather affects what form is being examined.
A backend is a presentation style for the source stepper. It prints information in the format required for the particular context in which the stepper is running. Examples:
The IDE has its own backend called :ide-stepper-dialog, which interacts with the user in a gui style and mostly uses mouse clicks and window presentations for stepping (see Stepper Dialog).
The coverage tool (see with-coverage) has its own backend called :coverage which does not actually interact with the user, but instead just records information and performs analysis when the macro is finished.
The line-oriented or tty stepper interface has two backends, one which has been available for many versions called :tty which provides complete source forms as output (which might unfortunately get very large when the source is complex), and a newer :flip-tty backend, which provides a more compact and readable format which, if viewed in a normal terminal screen, appears to be animated as if being viewed like a flip-book animated drawing set.
Other internal backends, like :trace and :dbg, which allow breakpoints to be printed for the functionalities that have specific presentation requirements.
These backends have used an internal, undocumented macro called excl::with-ldb-backend to temporarily switch from one backend to another. In addition, these backends can now be switched on demand by the :backend subcommand of the :break top-level command. It is only useful to switch between the two tty-style backends.
The command syntax is:
:br[eak] :backend
name
where name is the name of one of the tty style backends.
For 11.0 and later, the default backend is the :flip-tty backend. The :tty backend can be installed via
:br :backend :tty
and the default backend can be restored via
:br :backend :flip-tty
If a breakpoint was being displayed at the time of the command, it is redisplayed using the new backend.
Prior to 11.0, stepper output for Lisp source records was not very consistent. That style of output for each breakpoint depended on the type of breakpoint that was being displayed. The style includes complete form representation based on pretty-printing criteria, and location information that is needed to navigate the stepping process.
An example run is provided which contrasts the two tty-style backends here, with the :tty style being demonstrated here
As of 11.0 and later, the default style for stepping through lisp code is called flip-book style. It uses the :flip-tty ldb-backend, as opposed to the older style, which uses the :tty backend.
The :tty backend was cumbersome in that it was inconsistent in its formatting. The source form being printed might be one line, or even one word, or it might be 100 lines long due to the complexity of the source. Stepping though the source records force the user to look for the relevant information, and there is no continuity between the current breakpoint and any prior or upcoming breakpoints.
An example run is provided which contrasts the two tty-style backends here, with the :flip-tty style being demonstrated here
The :flip-tty backend shows records in a style which is similar to the format printed by print-function-meta-info, except that only the current breakpoint is shown along with a few records before and after it (the default is 2 records on either side). Because the output is always the same size, it is more consistent in its appearence, and if the terminal buffer or window is consistent in keeping the prompt at the bottom of the buffer or window, then successive typing of the "return" or "enter" key will cause the next steps to advance in such a way that the small group of records appear to be animated in-place, rather than moving in fits and starts.
For more info on the following source-record fields, see Source record description.
The current breakpoint is seen with a ->
arrow pointing to that record. Each group of up to 5 records is preceeded with a title line which describes the fields of each line:
rec/pc lev nxt(rec/pc) br(rec/pc) e-type br-type st end form
where
rec/pc
shows the record's number, a slash, and a relative program counter
lev
shows the possible levels that the breakpoint could be at (the actual level of the current breakpoint is shown later)
nxt
shows the record number and the relative program counter of the next record (unless the record is a jump).
br
shows the record number and relative program counter of the branch target if the record is a branch
e-type
shows the entry-type of the record. See entry type for a list of types
br-type
shows the branch type of the record. See branch type for a list of types
st
shows the start character of the record.
end
shows the end character of the record.
form
shows the source form of the record. *print-length* and *print-level* have been adjusted to ensure that this form prints on one line.
Note that as you step through code, not all records are necessarily stopped at. With the default step/slide type of over/0, only records that can be at level 0 will be stopped at. To step through all records in the current function, set the step/slide type to over/into via the top-level command:
:step over into
To step through all records in every function, into/into should be used:
:step into into
See :step in ldb mode for info on stepping in the source-level stepper.
The functions validate-lisp-source and print-function-meta-info print information about function definitions, compilation and debugging. The information is complex. The following sections describe aspects of the output and so provide a single description for things done by various functions.
A source record is printed specially by print-function-meta-info, because source records serve as breakpoints and the ldb-code struct which implements a source record inherits from the breakpoint struct. Each of these structs print differently (more concisely) when using their print-object methods. When print-function-meta-info is called with :mixed or :vars non-nil
and available, each source record line is prepended with "S", otherwise no "S" is prepended. Before the first record. a header is printed, which gives a general feel for the alignments of fields. Not all fields align perfectly on every record: some fields are longer than others. For orientation, a vertical bar (|) is placed between fields in two places, thus dividing the record into 3 sections.
Below is a chart of slot names and their field names in the printout. Also present is an indication of whether the field represents a breakpoint slot (otherwise it is a lisp record slot). Note that some field names are duplicated; they are short to save space and the correct sense can easily be seen by what section the field is in
slot name field name breakpoint slot? Comment
=========================================================
Start of record section
ics-passa i
index ix
level lev
prev-rec prev
next-rec nxt
branch-rec br
enclosing encl
end end
(separator) | Signals start of macroexpand section
parent par
child chld
(separator) | Signals start of pc section
pc pc yes
next-pc nxt yes
branch-pc br yes
entry-type e-type
branch-type b-type yes
start-char st
end-char end
source form
ics-passa: the first column (without the initial S character) or the second column (with the S character) is either blank, a +, or a -. Blank indicates no ics (16-bit vs 8 bit character set) distinction; the function is designed to run with either character width. For records with - in the ics-passa column, the function can only be run in 8-bit character set lisps, and with a + in that column, the function can only be run in a 16-bit character-set lisp. In any one lisp you should only to expect to see one of two characters in any print-function-meta-info output: either a blank or the character that corresponds to the correct character width.
index: the ix column shows the index of the record in the record set for this function. Note that several related functions might be associated with the same record set; for example, a parent function and its flet internal functions would all have the same source record set, so not all first records begin with a 0 index.
level: the level field is really two fields: a minimum and a maximum. These values provide boundaries on the macroexpand level on which this record may rise. Every macroexpansion constitutes an increment in the level. If the min and max are the same only one level is printed.
Level 0 records are special; any record which has 0 as its min level is a record which corresponds to actual source code. These records should also have character position information.
prev-rec, next-rec, and branch-rec: the prev field shows the index of the record which immediately precedes this one. Next (nxt) and branch (br) in the print-function-meta-info output, these fields specify one or more indexes each indicating where execution might continue from here.
enclosing: the enclosing field is the index of the record which immediately encloses this record. In a sense, the field points to the next more outer form.
end: if this record is a special operator or a binding form, the end field will point to the next form after this form, lexically.
parent and child: Like the next and branch fields, the parent (par
) and child (chld
) fields are each an index, indicating a macroexpansion relationship. The child field indicates that this record is a macro form: the child field indexes the record which represents the form this form expanded to (each expansion is a single macroexpansion, as with macroexpand-1 or excl::compiler-macroexpand-1). The parent field indexes the parent, and each pair always reference each other. If the child record is also a macro form, it will also have a child record which indexes a third record, which will in turn point back to it via the parent field.
pc, next-pc and branch-pc: labelled pc
, nxt
, and br
in the second half of each record in print-function-meta-info output, these values are actual relative program-counter values. A record has only one pc field, and may have 0 or more next-pc and branch-pc fields, depending on the branch-type.
entry-type: this field describes in a keyword what kind of record this is. nil
in this field means that there is no special consideration for this record. A non-exhaustive list of entry-types is:
:ref
- A variable is being referenced.:call
- A call to a function is about to occur; arguments have already been placed.:early
- A call to a function is being considered, but arguments have not yet been placed.:clone
- assembler code has split off into two or more branches in implementing this form, and this record is a copy of a previous record.:init
- This record represents the storing of an initialization form into a variable being bound by a let form.:setq
- A variable is about to be setq'd.branch-type: this field describes the branching characteristics of this record. If the field is nil, then no branching occurs; the record only contains one next or branch field, and thus only one next-pc or branch-pc field. The most common non-nil branch-type values are:
:bcc
- Branch on Condition Code: A decision will be made whether to branch or not. A next and branch field will always be present.:jmp
- branch unconditionally: Usually the branch/branch-pc fields will point to the target record/pc, and there will be no next/next-pc fields.:call
- A call to a function. The function is determined by what is in the function name register, and a decision about whether to break at the beginning of that function is determined by the step type. The next fields will be filled in with the next record after the call, unless it is a tail-call.:ret
- This record is a return record; there are no next or branch fields.:case
- This record represents a table-calculated jump, which may have next fields but always has multiple branch fields.:noreturn
- This record represents dead code, which will not be returned to (e.g. by a tail call).start-char: if the st field is filled in it represents the absolute 0-indexed character-position of the start of this form in its source file. If the field is a list of one integer, then it represents the macro which fully replaces the form starting at the same position. Multiple macroexpanded records might have the same start char as the highest parent in the chain, and all but that highest parent will have the start char in list form.
end-char: end is just like st, only when present it represents the character just past the closing parenthesis of the form. Like st, it may be a list of one integer, which represents a macroexpansion which precisely replaces the original macro form.
source: the form field is the a copy of the actual source for the current record. It is not the identical source that was read, because it went through a fasl writing/reading process to get to its current state. But all sharing is preserved within any fasl file, so subforms in different records may be eq to each other. Note that when print-function-meta-info prints this value it uses concise-printing to do so.
This description is only general, since different architectures have different assembler languages and thus different disassembler output. Also, this description only covers instructions: headers and other information normally printed by disassemble are not covered.
The general instruction format is
|<annotation>| <pc> <hex data> |[<real mnemonic>]
| <mnemonic> <args> |<comments>| |<more hex data>|
Where ||
denotes an optional field.
Descriptions:
A variable transition is always printed as an object, using the variable-transition's print-object method. Within the print-function-meta-info output, the first character of the line is always "V". The object is positioned in a column that generally aligns the pc value with other values in the print-function-meta-info output.
Fields that are printed are:
pc=X - this is the program counter that effects the transition. Sometimes this corresponds to actual data movement in the corresponding instruction, and sometimes it simply represents data that is no longer available, either from being overwritten by another variable or else simply no longer being visible.
trans=X - describes the transtion type. X can be one of :dead
, :use
, :reg-kill
, :continue-in
, :alive
, :def
, :def-arg
, and :continue-out
. See the Transition descriptions discussion below.
type=T - the type of the variable's location, where T can be
name=S - if the name S of the variable is available it is printed here
N - a lone integer represents the ordinal of the local. If local names are being saved with this function, then the local can be accessed with (aref (excl::fn_locals func) (1+ N))
where func is the function-object.
regname: only printed for :reg
type variable transitons, shows the name of the register which currently holds this variable's value.
As of Allegro CL version 11.0 transition types have been enhanced to become more acurate and descriptive.
In a perfect world, there would only be two transition types: :def
and :use
, which would describe the start of a live range for a variable (a la :def
, where the variable is defined), and the end of the live range (the last use of the variable, a la :use
). Pre-11.0 lisps note these two transition types and also :def-arg
transitions which represent function arguments receiving a value.
Challenges to this simple scheme:
Consider the following flow chart with basic-blocks (units of program flow which may be comprised of zero or more instructions, and which have one or more inputs and one or more outputs, but which are otherwise essentially linear within themselves). Within each basic block might be any number of quads, which are individual units of execution which have 4 parameters: one or more inputs, one or more outputs, and two extra qualifying arguments. Here we are considering three variables to be live: a, b, and c:
|
-----
| bb1 |
| | --- bcc quad: live variables: a, b, c
----- \
| \
| v
| -----
| | bb2 | any quad: live variables: a, b, c
v | |
----- -----
| bb3 | |
| | < - -/ - - - (setq c 10)
----- /
| /
| /
| /
|/
v
-----
| bb4 | any quad: live variables: a, b, c
| |
-----
|
...
|
|
v
-----
| bb10|
| | throw quad: (no live variables)
-----
(bb2's placement here).
The flow chart for this set of basic-blocks requires two dimensions to represent, but in order to place the code for these blocks, bb2
must be moved elsewhere. One way to do this is to just place the code for bb2
between bb1
and bb3
and create extra jumps from the end of bb1
to bb3
and from the end of bb2
to bb4
. Another way, which better preserves linearity, is to place the code for bb2
after the whole line of code has been placed (in this case after bb10
), and then to have a reverse branch from the end of bb2
to bb4
. The branch-on-condition quad (bcc) already has two targets, the fall-through and the branch target, so only one extra branch is generated when the code is placed. If furthermore the selection is made wisely as to whether it is bb2
or bb3
which is placed linearly, the most likely path through the function might be executed with the fewest branches.
If in bb3
the quad which executes the lisp source (setq c 10)
resides, then c will be live in bb1
but dead in the first part of bb3
, finally become alive again when the setq occurs.
Since the throw occurs on bb10
, variable c cannot be live after that throw quad. However, if bb2
has been placed after bb10
, variable c becomes alive again, as a continuation into the basic block by the code in bb1
. Likewise, any code afer bb2
will render c dead, because it just jumped back to bb4
.
Transition types, 11.0 and later
Starting in the 11.0 release, the variable transition types have been expanded more appropriately represent the challenges mentioned above.
:def
, but the live range started is specificically for a register argument, and not for a specific variable. Transitions like this usually have a name of (:param N)
for some small integer N, and the live ranges they create are usually very short.:use
transition. Typical :dead
transition appearance is immediately after a branch-on-condition (bcc) instruction, where the taken branch continues the liveness of the variable but the not-taken code will never use the variable. In the illistration above, the beginning of bb3
might have a :dead
transition for variable c because of the setq later on in bb3
.shadow
) location of the variable remains alive.:continue-in
transition continues the live range for the variable from where it was jumped to. In the flow chart above, the beginning of bb2
might have some :continue-in
transitions.:continue-out
transition. Note that this transition stops the live-range linearly, but not logically. So in the instruction immediately after the jump instruction, the variable is no longer alive because that instruction doesn't follow the jump instruction. Note also that this does not pertain to a conditional jump instruction where the variable is alive in both sides (i.e. the branch and the fall-through); there is no transition on either side.A census point is always printed as an object, using the census-point's print-object method. Within the print-function-meta-info output, the first character of the line is always "C". The object is positioned in a column that generally aligns the pc value with other values in the print-function-meta-info output.
Fields that are printed are:
pc=X
: this is the program counter that the garbage-collector will see when this census point is in effect. Usually, if the census point is a call instruction, that pc is actually the saved program counter after the call instruction.N objects
: a new area on the stack is reserved for all stack-allocated objects; this area is allocated on a last-in-first-out (LIFO) basis with respect to binding forms entered and exitted. This number N states the current number of live objects at this census point. The conservative gc will walk up this section of other objects until it finds a nil
as the first object, but the precise-gc can use this number to determine where to stop.bits=#xHHH
: a hexadecimal number HHH represents the pattern of live local variables. A 1 bit represents a live value and a 0 bit represents a dead value. Locals are numbered 0 through the total number of stack allocated locals, with the least significant bit of HHH being local 0. Thus, bits=#x1a means that local 1, local 3, and local 4 are live and all others are dead.The Allegro CL gdb interface is a mixture of gdb call commands and other low-level gdb commands to produce debugging output useful for lisp debugging. Some other debuggers, such as windbg on Windows systems, also provide a call command (it is called .call on windbg) and can thus also be used with this interface. On MacOS's lldb program, the expr command can be used.
Note this interface is by no means complete, and is known to have bugs. It has been tested on the following platforms:
There are currently 16 external functions and four variables in this interface. Eight of the functions are paired, differing only in where the output is directed. Five of the functions are abbreviations for longer ones that contain underscores in their names, and are provided for typing convenience.
Output a printed representation of the object to file-descriptor 1. obj is a tagged representation of a lisp value.
Current limitations: many, including:
Symbols are not qualified with their packages.
Many objects are only described in a very high-level way, such as <struct: 0x12345> rather than describing them in more detail, e.g. like stating the name of the struct.
Element types of vectors are not stated, except for strings, which are specially printed as strings.
Such objects are printed with angle-brackets only, rather than in a way that might confuse the reader into thinking that the printing was being done in a lispy way (such as #<...> for unreadable objects).
This interface has a notion of a "current" frame. When lisp_zo() (or lisp_zo_aux()) is successful, the frame it first finds is remembered as the current frame. Other functions can operate on the current frame to move up or down in the stack. Care must be taken to call lisp_zo() with a nonzero frame argument after a breakpoint, in case the running program has removed the stack frames that had been remembered by the debugger.
The arguments are unsigned nats, which are defined formally in the file misc/lisp.h (in the misc/ subdirectory of the Allegro directory), but which are typically positive integers. The out argument to the _aux functions should be a pointer to a C stream suitable for output. Variables named frame are expected to be pointers into the stack, but what registers might be used to start the stacks might vary. Most architectures have a stack pointer register generically named sp
and a frame pointer register named fp
. They might also be adorned with punctuation; gdb generally wants a register name to be preceded by a dollar sign ($
), but other debuggers might want either to see a percent sign (%
) or at (@
) sign. Also, some systems have alternate names for sp
and fp
- for gdb, $fp
might be $ebp
on 32-bit x86, $rbp
on x86-64, or $x29
on arm64/Apple-Silicon systems. The best value to pass into a lisp_zo() or equivalent function is $sp
on x86-64 and $fp
on 32-bit x86 and on arm64 architectures.
On Windows the functions below can be called by referring to them by their fully-qualified names including the library name; for example .call acli11b156!lisp_zo($sp,10)
Sometimes gdb will give a complaint about an invalid cast. In these cases, call (void)lisp_zo(...)
should work.
Print to file descriptor 1 a backtrace from the specified frame, for n frames. lisp_zo() prints to stdout, while lisp_zo_aux() prints to the location specified by the out argument.
Frames are printed in function call or funcall format when possible, otherwise just the names of the functions are printed as function objects. When a frame represents a C function, its saved program counter is printed in a C form. If the frame is a "runtime system" frame (linked in as C/asm, but lisp-savvy) it is annotated as "rs:".
If frame is given as 0, then the current frame is used to start the output. The current frame is determined as follows:
The lisp_zo() function now heeds a "focus", that is, a particular thread which is pre-selected. The focus is set by lisp_focus(), described below.
Current limitations include:
locals are not printed.
Some architectures can't yet walk the stack.
Some architectures don't support dladdr() functionality, and so the C frames printed as "
If a frame appears to be a lisp frame, but doesn't have a valid function object in the expected spot, then the frame is annotated as "incomplete:" and an attempt to print it as a C frame is made. This tends to happen at the beginning of a function where the stack has not yet been completely built, or at the end of a backtrace where the stack is a little ragged on some architectures.
Prints the current frame, if there is one. May fail catastrophically if no current frame exists.
Moves up n frames, where "up" is the direction toward the top, or frontier, of the stack, then prints the new current frame. Note that this is the same concept of "up" as Allegro CL debugger has, and opposite of gdb.
Moves down n frames, where "down" is the direction away from the top, or frontier, of the stack, then prints the new current frame. Note that this is the same concept of "down" as Allegro CL debugger has, and opposite of gdb.
These two variables form a parallel to *print-level* and *print-length* in Lisp. They must be integers, though; do not set them to nil
(i.e. nilval). Currently, they only affect the printing of lists.
If non-null, these variables hook into the printing of non-lisp names; when in the gdb interface a frame is being printed which might be a C++ name, the lisp_demangle_hook
is called, which should either return a non-null string which has been demangled, or a NULL (which will result in the frame-printer using the original string). After the name is printed, if the original name was demangled (i.e. the hook returned a non-null string) and if the lisp_demangle_deallocate_hook
is non-null, then that hook is called in order to deallocate the string which might have been allocated by the demangle hook. Users should ensure that these hooks match in their operation; e.g. deallocation should not occur if there was never an allocation by the demangling functionality.
Set up to focus on the thread specified by threadspec. After the function is run any lisp_zo, lisp_cur, lisp_up, or lisp_dn calls will pertain only to that sepected thread's stack.
threadspec can be one of
Once the thread is selected, the "current" frame (i.e. what will print if lisp_zo is given a first argument of 0) will be adjusted appropriately and a message will be shown in stdout which indicates what stack frame will be current. From there, lisp_zo, lisp_cur, lisp_up, and lisp_dn can be used to navigate frames within the focussed thread.
If running in an SMP lisp, care must be taken when focussing on a thread that is not the current thread, because threads are not stoppped like they are in the lisp debugger with the :focus top-level-command, and stack addresses which were valid in one invocation of lisp_zo may likely not be valid in the next invocation.
For virtual-threaded lisps, there is only one stack, and a thread-switch operation is performed to offload the current stack to the saved area for that thread and to then load the saved stack into the stack area for the thread that will start executing next. The thread switch operation is tracked by a counter, and if the number of thread switches has changed between one invocation of lisp_zo and the next, the concept of "current" stack frame is reset and the cache of saved frames is erased.
A struct which controls all Allegro CL threads. Two useful slots are
registry
: An array of threadctl* structs which are the currently registered lisp threads in the system.heap_owner
: (only valid in non-SMP lisps) which note which thread currently has control of the heap. -1 indicates no current heap owner. The heap owner value is an index into the registry slot of which thread currently has the right to touch (either read from or write to) the heap.A 4096-integer array which may hold cached stack frames.
An integer index into the lisp_frame_cache. If lisp_frame_cache[lisp_frame_current_index] is non-zero, then it represents the (purportedly valid) current stack frame.
See Example 2 for usage for 64-bit lisps on Intel chips
At a prompt on a linux 32-bit lisp:
Note here that:
The stack frame is denoted by $ebp. Sometimes $fp can be used, but be sure it truly does represent the current value of the frame (on some architectures gdb makes a best guess as to what was "meant" by the frame-pointer in specifying $fp, and it doesn't always match the value of $ebp).
The second arg is 0, asking for as many frames as possible.
(gdb) call lisp_zo($ebp,0)
0xbf83a538: <unknown C name>
0xbf83a870: (read-octets <stream: 0x71208f92> nil 0 4096 peek)
0xbf83a8d8: (funcall #'(((method) . device-read) terminal-simple-stream t t t t) <stream: 0x71208f92> nil 0 nil peek)
0xbf83a958: (funcall #'(((internal) (((#))) . t) . 0) <stream: 0x71208f92> nil 0 nil peek)
0xbf83a9a0: (erroring-device-read <stream: 0x71208f92> nil 0 nil peek)
0xbf83aa38: #'(((efft) . dc-read-char) . latin1-base)
0xbf83aa90: (simple-stream-peek-char nil <stream: 0x71208f92> nil eof)
0xbf83aae8: (peek-char nil <stream: 0x71208f92> nil eof)
0xbf83ab28: (peek-char-non-whitespace <stream: 0x71208f92>)
0xbf83ac68: (read-top-level-command <stream: 0x71208f92>)
0xbf83af40: (read-eval-print-one-command nil nil)
0xbf83b2a0: (read-eval-print-loop continue-error-string nil condition <standard-instance: 0x71a9b802>)
0xbf83b560: (internal-invoke-debugger "Error" <standard-instance: 0x71a9b802> t)
0xbf83b640: (error <standard-instance: 0x71a9b802>)
0xbf83b8b8: (unbound-variable-handler a)
0xbf83b900: (general-error-handler 5 a t t)
0xbf83b938: (cer-general-error-handler-one 5 a)
0xbf83ba08: rs: stopped at "unbound+158"
0xbf83bab0: (%eval a)
0xbf83bb20: (eval a)
0xbf83bdf8: (read-eval-print-one-command nil nil)
0xbf83c158: (read-eval-print-loop level 0)
0xbf83c1d0: (top-level-read-eval-print-loop1)
0xbf83c228: (top-level-read-eval-print-loop)
0xbf83c270: rs: stopped at "apply+311"
0xbf83c300: (start-interactive-top-level <stream: 0x71208f92> #'top-level-read-eval-print-loop nil)
0xbf83c5a0: (start-lisp-execution t)
0xbf83c5f0: rs: stopped at "apply+311"
0xbf83c6b8: (thread-reset-catcher)
0xbf83c7e8: (setup-required-thread-bindings)
0xbf83c8d8: (run-initial-thread t)
0xbf83c980: (lisp-thread-start #S(thread:0xa0001822 <bad object: 0x845972> <bad object: 0x845972> "Initial Lisp Listener" <standard-instance: 0x71a6a1e2> nil #'start-lisp-execution-0 (t) nil nil...) 1)
0xbf83c9d8: rs: stopped at "start_reborn_lisp+76"
0xbf83ca48: rs: stopped at "startup_lisp+11"
0xbf83ca68: stopped at "cont_setstack+371"
0xbf83cac0: rs: stopped at "first_lisp_thread_init+274"
0xbf83cb18: stopped at "setupstack_within_xhandler+16"
(gdb)
We can move down a few frames:
(gdb) call lisp_dn(2)
0xbf83a8d8: (funcall #'(((method) . device-read) terminal-simple-stream t t t t) <stream: 0x71208f92> nil 0 nil peek)
(gdb) call lisp_dn(1)
0xbf83a958: (funcall #'(((internal) (((#))) . t) . 0) <stream: 0x71208f92> nil 0 nil peek)
(gdb)
Here, we've selected a frame to print, and asked for two frames.
(gdb) call lisp_zo(0xbf83a958,2)
0xbf83a958: (funcall #'(((internal) (((#))) . t) . 0) <stream: 0x71208f92> nil 0 nil peek)
0xbf83a9a0: (erroring-device-read <stream: 0x71208f92> nil 0 nil peek)
(gdb)
Note above that not all of the first frame's name is printed. If we grab the function object from the frame, print it, and then print it after increasing the lisp_print_level, then we see the whole name:
(gdb) x/16x 0xbf83a958-16
0xbf83a948: 0xfffffffa 0x719ed7f2 0x00000570 0x711d0a42
0xbf83a958: 0xbf83a9a0 0x71259d71 0x71208f92 0x71000685
0xbf83a968: 0x00000000 0x71000685 0x711c48ff 0x00000040
0xbf83a978: 0x71208f92 0xa0001822 0x71000685 0xa0001822
(gdb) call lisp_output_object(0x711d0a42)
#'(((internal) (((#))) . t) . 0)
(gdb) p lisp_print_level
$1 = 5
(gdb) set lisp_print_level=10
(gdb) call lisp_output_object(0x711d0a42)
#'(((internal) (((((effective-method) . 5)))) . t) . 0)
(gdb)
Note that the name is of an "internal" fspec form. Note also that since the symbols don't print their packages, calling the internal function to externalize the name doesn't fix it:
1): (excl::convert-to-external-fspec '(((internal) (((((effective-method) . 5)))) . t) . 0))
cl-user(t) . 0)
(((internal) (((#))) . 2): cl-user(
However, adding package qualifiers does fix this (this nested fspec happens to have two function specs in the keyword package):
2): (excl::convert-to-external-fspec '(((:internal) (((((:effective-method) . 5)))) . t) . 0))
cl-user(5 nil nil nil t) 0)
(:internal (:effective-method 3): cl-user(
Here is the backtrace for Linux/amd64. Note that we use $rsp for the stack frame, and that there are a couple of anomalies at the end of the backtrace (the last frame is mistaken for a Lisp frame, which makes it appear to be incomplete as a lisp frame, and also that incompleteness causes a failure to find the next frame, which is hard in general to do on any architecture which does not store an explicit next-link into its stack frames).
(gdb) call lisp_zo($rsp,0)
0x7fff36d74968: stopped at "cl_select_read+300"
0x7fff36d769e0: (read-octets <stream: 0x100032dfe2> nil 0 nil peek)
0x7fff36d76e40: (funcall #'(((method) . device-read) terminal-simple-stream t t t t) <stream: 0x100032dfe2> nil 0 nil peek)
0x7fff36d76f30: (funcall #'(((internal) (((#))) . t) . 0) <stream: 0x100032dfe2> nil 0 nil peek)
0x7fff36d77040: (erroring-device-read <stream: 0x100032dfe2> nil 0 nil peek)
0x7fff36d770d0: #'(((efft) . dc-read-char) . latin1-base)
0x7fff36d771b0: (simple-stream-peek-char nil <stream: 0x100032dfe2> nil eof)
0x7fff36d77280: (peek-char-non-whitespace <stream: 0x100032dfe2>)
0x7fff36d77310: (read-top-level-command <stream: 0x100032dfe2>)
0x7fff36d77560: (read-eval-print-one-command nil nil)
0x7fff36d77ae0: (read-eval-print-loop continue-error-string nil condition <standard-instance: 0x1001136932>)
0x7fff36d78170: (internal-invoke-debugger "Error" <standard-instance: 0x1001136932> t)
0x7fff36d786e0: (error <standard-instance: 0x1001136932>)
0x7fff36d788a0: (unbound-variable-handler a)
0x7fff36d78d60: (general-error-handler 5 a t t)
0x7fff36d78e20: rs: stopped at "unbound+355"
0x7fff36d79240: (%eval a)
0x7fff36d79330: (eval a)
0x7fff36d79430: (read-eval-print-one-command nil nil)
0x7fff36d799b0: (read-eval-print-loop level 0)
0x7fff36d7a040: (top-level-read-eval-print-loop1)
0x7fff36d7a140: (top-level-read-eval-print-loop)
0x7fff36d7a200: rs: stopped at "apply+479"
0x7fff36d7a2b0: (start-interactive-top-level <stream: 0x100032dfe2> #'top-level-read-eval-print-loop nil)
0x7fff36d7a3d0: (start-lisp-execution t)
0x7fff36d7a8a0: rs: stopped at "apply+479"
0x7fff36d7a960: (thread-reset-catcher)
0x7fff36d7aae0: (setup-required-thread-bindings)
0x7fff36d7ad20: (run-initial-thread t)
0x7fff36d7aef0: (lisp-thread-start #S(thread:0x20000018b2 <bad object: 0x2aaaaad3b392> <bad object: 0x2aaaaad3b392> "Initial Lisp Listener" <standard-instance: 0x1001106462> nil #'start-lisp-execution-0 (t) nil nil...) 1)
0x7fff36d7b050: rs: stopped at "first_lisp_thread+584"
0x7fff36d7b110: rs: stopped at "start_reborn_lisp+140"
0x7fff36d7b1f0:
Terminating frame 0x7fff36d7b1f0: can't find valid next frame
incomplete: stopped at "startup_lisp+14"
Terminating frame 0x7fff36d7b1f0: can't find valid next frame
(gdb)
Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |