Chapter 4.  Debugging Facilities

Table of Contents

Passive Debugging
Active Debugging
Runtime Tracing
Breakpoints

No Scheme system would be complete without facilities to assist the programmer in debugging his or her code. SISC provides aid for passive debugging (requiring no action on the part of the programmer) and active debugging (requiring code instrumentation to facilitate debugging).

Passive Debugging

Passive debugging facilities are provided that collect information on an error that occurred at runtime and was not caught by the executing code. The programmer can then inspect the last error, obtain information about the call stack of the error, or even attempt to restart the computation.

procedure: (get-last-exception) => exception

Retrieves the last exception that occurred in SISC.

One of the most common desires is to obtain a trace of the call stack, to determine what sequence of calls resulted in the error. SISC provides procedures for accessing the call stack of any continuation.

Requires: (import debugging)

procedure: (stack-trace continuation) => list

Returns the stack trace for continuation in form of a list. The format of the list is

stack-trace:=(call-frame ...)
call-frame:=sisc-expr | (sisc-expr overflown . stack)
overflown:=#t | #f
stack:=(stack-entry ...)
stack-entry:=sisc-expr | (repetitions . stack)
repetitions:=integer

Each element in the list represents one level in the SISC interpreter's call stack, starting from the top. The element contains the SISC virtual machine expression that would be executed next in that frame, and, if available, a compact representation of a virtual stack created by collecting information on tail calls carried out in that frame.

The virtual stack is bounded in size by the value of the see maxStackTraceDepth configuration parameter (see the section called “Configuration Parameters”). If old information was dropped due to the bound being exceeded then the overflown flag is set.

Each entry in the virtual stack contains either the SISC virtual machine expression that was executed, or a sub-stack annotated with a repetition count, indicating that the entries in that sub-stack were repeated several times.

SISC expressions are annotated with source locations if the emitAnnotations parameter is set to true. Additional annotations are produced when emitDebuggingSymbols is set to true. See the section called “Configuration Parameters”. The annotations can be retrieved using the annotation function with the keys source-file, line-number, column-number, source-kind, and proc-name.

Stack trace entries for expressions with a source-kind mentioned on suppressed-stack-trace-source-kinds are suppressed.

procedure: (suppressed-stack-trace-source-kinds [list]) => list

Retrieves or sets the list of source kinds to suppress in stack traces returned by stack-trace. This is a dynamic parameter.

The default value is (#f) which causes all stack trace entries for expressions with no specified source kind to be suppressed.

The annotation of expressions with source kinds and other information is controlled by the source-annotations parameter.

procedure: (source-annotations [alist]) => alist

Retrieves or sets the association list of additional annotations for expressions that are being read. This is a dynamic parameter.

All system and core library code is loaded with this parameter set to (), resulting in no additional annotations being produced. However, on entry to the REPL, the parameter is set to ((source-kind . user)). In combination with the default settings for suppressed-stack-trace-source-kinds this results in system code being omitted from stack traces.

procedure: (print-stack-trace continuation)

Displays the call stack of the continuation in a human-readable form.

The error message, location information and call stack associated with an exception can be displayed in human-readable form using the following procedure.

procedure: (print-exception exception [stack-trace?])

Displays the error message and location of exception. A stack trace is displayed if stack-trace? is absent or set to #t. Furthermore the procedure calls itself recursively in order to display similar information for nested exceptions.

In order to obtain the source file location of a call, your Scheme code must have been loaded while SISC's reader had annotations enabled. Annotations are a means of attaching metadata to compiled Scheme code. To allow the reader to attach annotations related to the source file position of the code it reads, enable the emission of annotations with the emitAnnotations configuration parameter (see the section called “Configuration Parameters”).

SISC can also produce more detailed stack traces if code was generated with debugging symbols. These are extra annotations generated by the compiler that track function and variable names that would ordinarily be discarded. By including these annotations, the stack trace can display the name of more of the calls involved. To enable the generation of debugging symbols, the emitDebuggingSymbols configuration parameter must be set to true (see the section called “Configuration Parameters”).

Finally, when debugging a program for a long period of time, it may be desirable to have stack traces displayed whenever an error occurs, rather than needing to invoke print-exception or other functions each time. For this, the stackTraceOnError configuratin parameter must be set to true (see the section called “Configuration Parameters”).

Active Debugging

Requires: (import debugging)

SISC provides active debugging aids; procedures and syntax that can be used in source code to assist in tracing the activities of running Scheme code.

Runtime Tracing

When a function is traced, each call to the function will be displayed to the console with the function's trace identifier and the values of the operands the function is being applied to. Each nested call is indented slightly, so as to illustrate the depth of calls. When the function application returns, the value of the function-call is displayed at the same indentation as the call itself. Once indented to a certain depth, the same indentation is kept for further nesting, but the depth of the call is displayed as an integer preceding the call.

syntax: (trace-lambda trace-name formals body) => procedure

When replaced with a trace-lambda, all calls to a lambda defined procedure are traced on the console. trace-name is an identifier which will disambiguate the procedure in the trace. formals and body have identical semantics to lambda.

syntax: (trace-let loop-identifier formal-bindings body) => value

Replaces a named-let expression in a similar manner to trace-lambda.

procedure: (trace [symbol] ...) => undefined

Begins traces on the procedures named by the symbols given. The procedures must be defined in the top-level environment.

If no procedures are given, a message is displayed indicating the names of top-level procedures currently being traced.

If a traced procedure is redefined, it will not retain the instrumenting installed by trace, until trace or untrace is called again (with any arguments). At that time, the traced procedures are reinspected and instrumenting reinstalled on redefined procedures.

procedure: (untrace [symbol] ...) => undefined

Stops tracing the top-level procedures named by the symbols given.

If no procedures are given, a message is displayed indicating the names of top-level procedures currently being traced.

trace-lambda and trace-let are useful for debugging anonymous lambdas and named-lets respectively. trace and untrace ar useful for tracing any top-level bound procedure, including calls to builtin procedures and stored continuations.

Note

Tracing a function installs instrumentation code around the procedure which does not preserve the continuation of a call to that function. Thus, tail calls made in a traced function are no longer tail calls. This may affect the memory usage characteristics of running code.

Breakpoints

A user may wish to halt execution of a running Scheme program when a given procedure is called. SISC provides means to install breakpoints at top-level visible functions without having to redefine the function.

When a breakpoint is set using set-breakpoint!, and the function is called, execution will halt, returning to the REPL and displaying an informational message indicating a break, the procedure called, the arguments passed to the breakpointed procedure, and, if possible, the location in a source file of the call. The user may then continue execution using the continue procedure.

procedure: (set-breakpoint! symbol) => undefined

Instruments the top-level procedure named by the given symbol, such that when called, execution will halt and return to the REPL and the name of the breakpointed function and its arguments are displayed.

procedure: (clear-breakpoint! symbol) => undefined

Removes the instrumentation on the named top-level procedure, if present. Execution will continue normally through occurances of the formally breakpointed procedure.

procedure: (continue) => does not return

Continues execution from the most recent break. It is an error to call this procedure if a breakpoint has not been hit, or to call this procedure more than once for a given break.

procedure: (current-breakpoint-continuation) => continuation

Returns the continuation of the most recent breakpoint, or #f if execution is not currently interrupted at a breakpoint.

The continuation is useful for obtaining stack traces, e.g. with (print-stack-trace (current-breakpoint-continuation)).

procedure: (current-breakpoint-args) => list

Returns a list containing the current breakpoint's continuation procedure and arguments that will be used when execution is resumed with continue, or #f if execution is not currently interrupted at a breakpoint.

This procedure is useful for performing deep inspection of the breakpointed procedure and its arguments. The returned values are also handy for constructing modified breakpoint continuations with set-current-breakpoint-args!.

procedure: (set-current-breakpoint-args! procedure value ...) => #t/#f

Sets the current breakpoint's continuation procedure and arguments that will be used when execution is resumed with continue. If execution is not currently interrupted at a breakpoint then invoking this procedure has no effect and it returns #f. Otherwise it returns #t.