Go to the first, previous, next, last section, table of contents.
This chapter describes the debugging facilities that are available in
Development Systems. The purpose of these facilities is to provide
information concerning the control flow of your program.
In the Muse Development System, the debugger effectively sequentializes
the execution. Other tools (see section Visualization Tools) must be used
to debug the parallel aspects of execution.
The main features of the debugging package are as follows:
-
The Procedure Box model of Prolog execution which provides a
simple way of visualizing control flow, especially during backtracking.
Control flow is viewed at the predicate level, rather than at the level
of individual clauses.
-
The ability to exhaustively trace your program or to selectively set
spy-points. Spy-points allow you to nominate interesting
predicates at which the program is to pause so that you can interact.
-
The wide choice of control and information options available during
debugging.
The Procedure Box model of execution is also called the Byrd Box model
after its inventor, Lawrence Byrd.
Much of the information in this chapter is also in Chapter eight of
[Clocksin & Mellish 81] which is recommended as an introduction.
Unless otherwise stated, the debugger print goals using
write_term/3
with the value of the Prolog flag
debugger_print_options
(see section Information about the State of the Program).
The debugger is not available in Runtime Systems and the predicates
defined in this chapter are undefined; see section Runtime Systems.
During debugging, the debugger prints out a sequence of goals in
various states of instantiation in order to show the state the program
has reached in its execution. However, in order to understand what is
occurring it is necessary to understand when and why the debugger
prints out goals. As in other programming languages, key points of
interest are predicate entry and return, but in Prolog there is the
additional complexity of backtracking. One of the major confusions that
novice Prolog programmers have to face is the question of what actually
happens when a goal fails and the system suddenly starts backtracking.
The Procedure Box model of Prolog execution views program control flow
in terms of movement about the program text. This model provides a
basis for the debugging mechanism in Development Systems, and enables
the user to view the behavior of the program in a consistent way.
Let us look at an example Prolog predicate :
*--------------------------------------*
Call | | Exit
---------> + descendant(X,Y) :- offspring(X,Y). + --------->
| |
| descendant(X,Z) :- |
<--------- + offspring(X,Y), descendant(Y,Z). + <---------
Fail | | Redo
*-------------------+------------------*
|
<------------------------------+
Exception
The first clause states that Y is a descendant of X if
Y is an offspring of X, and the second clause states that
Z is a descendant of X if Y is an offspring of X
and if Z is a descendant of Y. In the diagram a box has been
drawn around the whole predicate and labeled arrows indicate the
control flow in and out of this box. There are five such arrows which
we shall look at in turn.
- Call
-
This arrow represents initial invocation of the predicate. When a goal
of the form
descendant(X,Y)
is required to be satisfied, control
passes through the Call port of the descendant box with the
intention of matching a component clause and then satisfying any
subgoals in the body of that clause. Note that this is independent of
whether such a match is possible; i.e. first the box is called, and then
the attempt to match takes place. Textually we can imagine moving to
the code for descendant when meeting a call to descendant in some other
part of the code.
- Exit
-
This arrow represents a successful return from the predicate. This
occurs when the initial goal has been unified with one of the component
clauses and any subgoals have been satisfied. Control now passes out of
the Exit port of the descendant box. Textually we stop following
the code for descendant and go back to the place we came from.
- Redo
-
This arrow indicates that a subsequent goal has failed and that the system
is backtracking in an attempt to find alternatives to previous solutions.
Control passes through the Redo port of the descendant box. An attempt
will now be made to resatisfy one of the component subgoals in the body of
the clause that last succeeded; or, if that fails, to completely rematch
the original goal with an alternative clause and then try to satisfy any
subgoals in the body of this new clause. Textually we follow the code
backwards up the way we came looking for new ways of succeeding, possibly
dropping down on to another clause and following that if necessary.
- Fail
-
This arrow represents a failure of the initial goal, which might occur
if no clause is matched, or if subgoals are never satisfied, or if any
solution produced is always rejected by later processing. Control now
passes out of the Fail port of the descendant box and the system
continues to backtrack. Textually we move back to the code which called
this predicate and keep moving backwards up the code looking for choice
points.
- Exception
-
This arrow represents an exception which was raised in the initial goal,
either by a call to
raise_exception/1
or by an error in a
built-in predicate. See section Error and Exception Handling. Control now passes out of the
Exception port of the descendant box and the systems continues to
pass the exception to outer levels. Textually we move back to the code
which called this predicate and keep moving backwards up the code
looking for a call to on_exception/3
.
In terms of this model, the information we get about the procedure box
is only the control flow through these five ports. This means that at
this level we are not concerned with which clause matches, and how any
subgoals are satisfied, but rather we only wish to know the initial goal
and the final outcome. However, it can be seen that whenever we are
trying to satisfy subgoals, what we are actually doing is passing
through the ports of their respective boxes. If we were to
follow this, then we would have complete information about the control
flow inside the procedure box.
Note that the box we have drawn round the predicate should really be
seen as an invocation box. That is, there will be a different box
for each different invocation of the predicate. Obviously, with
something like a recursive predicate, there will be many different
Calls and Exits in the control flow, but these will be for
different invocations. Since this might get confusing each invocation
box is given a unique integer identifier.
Development Systems provide a range of built-in predicates for control
of the debugging facilities. The most basic predicates are as follows:
debug
-
Switches the debugger on, and ensures that the next time control reaches
a spy-point, a message will be produced and you will be prompted for a
command. In order for the full range of control flow information to be
available it is necessary to have this on from the start. When it is
off the system does not remember invocations that are being executed.
(This is because it is expensive and not required for normal running of
programs.) You can switch Debug Mode on in the middle of
execution, either from within your program or after a ^C (see
trace/0
below), but information prior to this will just be
unavailable.
zip
-
Same as
debug/0
, except no debugging information is being
collected, and so is almost as fast as running with the debugger
switched off.
trace
-
Switches the debugger on, and ensures that the
next time control enters an invocation box, a message will be produced and
you will be prompted for a command. The effect of trace can also be achieved
by typing t after a ^C interruption of a program.
At this point you have a number of options. See section Options Available during Debugging. In
particular, you can just type RET to creep (or single-step)
into your program. If you continue to creep through your program you
will see every entry and exit to/from every invocation box, including
compiled code, except for code belonging to hidden modules
(see section Defining Modules). You will
notice that the debugger stops at all ports. However, if this is not
what you want, the following built-in predicate gives full control over
the ports at which you are prompted:
leash(+Mode)
-
Leashing Mode is set to Mode. Leashing Mode determines the ports
of invocation boxes at which you are to be prompted when you Creep
through your program. At unleashed ports a tracing message is still
output, but program execution does not stop to allow user interaction.
Note that the ports of spy-points are always leashed (and cannot be
unleashed). Mode can be a subset of the following, specified as a
list:
call
-
Prompt on Call.
exit
-
Prompt on Exit.
redo
-
Prompt on Redo.
fail
-
Prompt on Fail.
exception
-
Prompt on Exception.
The initial value of Leashing Mode is
[call,exit,redo,fail,exception]
(full leashing).
nodebug
-
notrace
-
nozip
-
Switches the debugger off. If there are any spy-points set then they
will be kept but disabled.
debugging
-
Prints information about the current
debugging state. This will show:
-
Whether undefined predicates are being trapped.
-
Whether the debugger is switched on.
-
What spy-points have been set (see below).
-
What mode of leashing is in force (see below).
For programs of any size, it is clearly impractical to creep through the
entire program. Spy-points make it possible to stop the program
whenever it gets to a particular predicate which is of interest. Once
there, one can set further spy-points in order to catch the control flow
a bit further on, or one can start creeping.
Setting a spy-point on a predicate indicates that you wish to see all
control flow through the various ports of its invocation boxes, except
during skips. When control passes through any port of a invocation box
with a spy-point set on it, a message is output and the user is asked to
interact. Note that the current mode of leashing does not affect
spy-points: user interaction is requested on every port.
Spy-points are set and removed by the following built-in predicates.
The first two are also standard operators:
spy :Spec
-
Sets spy-points on all the predicates given by Spec, which is
is of the form:
- Name
-
all predicates called Name no matter what arity,
where Name is an atom for a specific name or a variable for all names, or
- Name/Arity
-
the predicate of that name and arity, or
- Name/Low-High
-
- Name/(Low-High)
-
the predicates of that name with arity in the range Low-High, or
- Module:Spec
-
specifying a particular module (see section Module Prefixing) instead of the
default type-in module, where Module is an atom for a specific
module or a variable for all modules, or
- [Spec,...,Spec]
-
the set of all predicates covered by the Specs.
You cannot place a spy-point on an undefined predicate. If you set some
spy-points when the debugger is switched off then it will be
automatically switched on. Examples:
| ?- spy [user:p, m:q/(2-3)].
| ?- spy m:[p/1, q/1].
nospy :Spec
-
Similar to
spy Spec
except that all the predicates given
by Spec will have previously set spy-points removed from them.
nospyall
-
Removes all the spy-points that have been set.
spypoint_condition(:Goal,?Port,+Test)
-
Sets a conditional spy-point on the predicate for Goal.
When the debugger reaches the spy-point, three conditions must be met
for the spy-point to apply: The actual goal must match Goal, the
actual port must match Port, and Test (an arbitrary Prolog
goal) must succeed.
The options available when you arrive at a spy-point are described later.
See section Options Available during Debugging.
We shall now look at the exact format of the message output by the
system at a port. All trace messages are output to the standard error
stream. This allows you to trace programs while they are performing
file I/O. The basic format is as follows:
E N S 23 6 Call: T foo(hello,there,_123) ?
E is only used at Exception ports and displays a pending exception.
N is only used at Exit ports and indicates whether the invocation
could backtrack and find alternative solutions. Unintended
non-determinism is a source of inefficiency, and this annotation can
help spot such efficiency bugs. It is printed as `?', indicating
that foo/3
could backtrack and find alternative solutions, or
` ' otherwise.
S is a spy-point indicator. It is printed as `+', indicating
that there is a spy-point on foo/3
, or ` ', denoting no
spy-point.
The first number is the unique invocation identifier. It is
nondecreasing regardless of whether or not you are actually seeing the
invocations (provided that the debugger is switched on). This number can
be used to cross correlate the trace messages for the various ports,
since it is unique for every invocation. It will also give an
indication of the number of procedure calls made since the start of the
execution. The invocation counter starts again for every fresh
execution of a command, and it is also reset when retries (see later)
are performed.
The number following this is the current depth; i.e. the number
of direct ancestors this goal has.
The next word specifies the particular port (Call, Exit, Redo, Fail, or
Exception).
T is a subterm trace. This is used in conjunction with the
`^' command (set subterm), described below. If a subterm has been
selected, T is printed as the sequence of commands used to select
the subterm. Normally, however, T is printed as ` ',
indicating that no subterm has been selected.
The goal is then printed so that you can inspect its current instantiation
state.
The final `?' is the prompt indicating that you should type in one of the
option codes allowed (see section Options Available during Debugging). If this particular port is
unleashed then you will obviously not get this prompt since you have
specified that you do not wish to interact at this point.
Note that calls that are compiled in-line and built-in predicates that
are called directly from the top level are not traced.
There are two exceptions to the above debugger message format. A message
S - - Block: p(_133)
indicates that the debugger has encountered a blocked goal, i.e.
one which is temporarily suspended due to insufficiently instantiated
arguments (see section Procedural Semantics). No interaction takes place at this
point, and the debugger simply proceeds to the next goal in the
execution stream. The suspended goal will be eligible for execution
once the blocking condition ceases to exist, at which time a message
S - - Unblock: p(_133)
is printed.
This section describes the particular options that are available when
the system prompts you after printing out a debugging message. All the
options are one letter mnemonics, some of which can be optionally
followed by a decimal integer. They are read from the standard input stream with any
blanks being completely ignored up to the end of the line (RET).
Some options only actually require the terminator; e.g. the creep
option, as we have already seen, only requires RET.
The only option which you really have to remember is `h' (followed
by RET). This provides help in the form of the following list of
available options.
RET creep c creep
l leap z zip
s skip s <i> skip i
o out o <n> out n
q q-skip q <i> q-skip i
r retry r <i> retry i
f fail f <i> fail i
d display w write
p print p <i> print partial
g ancestors g <n> ancestors n
t backtrace t <n> backtrace n
& blocked goals & <n> nth blocked goal
n nodebug = debugging
+ spy this + <i> spy conditionally
- nospy this . find this
a abort b break
@ command u unify
e raise exception
< reset printdepth < <n> set printdepth
^ reset subterm ^ <n> set subterm
? help h help
- c
-
- RET
-
creep causes the debugger to single-step to the very next port
and print a message. Then if the port is leashed (see section Basic Debugging Predicates), the
user is prompted for further interaction. Otherwise, it continues creeping.
If leashing is off, creep is the same as leap (see below) except that a
complete trace is printed on the standard error stream.
- l
-
leap causes the debugger to resume running your program, only
stopping when a spy-point is reached (or when the program terminates).
Leaping can thus be used to follow the execution at a higher level than
exhaustive tracing. All you need to do is to set spy-points on an
evenly spread set of pertinent predicates, and then follow the control
flow through these by leaping from one to the other. Debugging
information is collected while leaping, so when a spy-points is
reached, it is possible to inspect the ancestor goals, or creep into
them upon entry to Redo ports.
- z
-
zip is like leap, except no debugging information is
being collected while zipping, resulting in significant
savings in memory and execution time.
- s
-
skip is only valid for Call and Redo ports. It skips over the
entire execution of the predicate. That is, you will not see anything
until control comes back to this predicate (at either the Exit port or
the Fail port). Skip is particularly useful while creeping since it
guarantees that control will be returned after the (possibly complex)
execution within the box. If you skip then no message at all will
appear until control returns. This includes calls to predicates with
spy-points set; they will be masked out during the skip. No debugging
information is being collected while skipping.
If you supply an integer argument, then this should denote an
invocation number of an ancestral goal. The system tries to get you
to the Exit or Fail port of the invocation box you have specified.
- o
-
out is a shorthand for skipping to the Exit or Fail port of the
immediate ancestor goal. If you supply an integer argument n,
it denotes skipping to the Exit or Fail port of the nth ancestor goal.
- q
-
quasi-skip is like a combination of leap and skip:
execution stops when either control comes back to this predicate, or
a spy-point is reached. No debugging information is
being collected while quasi-skipping.
An integer argument can be supplied as for skip.
- r
-
retry can be used at any of the four ports (although at the Call
port it has no effect). It transfers control back to the Call port of
the box. This allows you to restart an invocation when, for example,
you find yourself leaving with some weird result. The state of
execution is exactly the same as when you originally called, (unless you
use side effects in your program; i.e. asserts etc. will not be undone).
When a retry is performed the invocation counter is reset so that
counting will continue from the current invocation number regardless of
what happened before the retry. This is in accord with the fact that
you have, in executional terms, returned to the state before anything
else was called.
If you supply an integer argument, then this should denote an
invocation number of an ancestral goal.
The system tries to get you to the Call port of the box you have
specified. It does this by continuously failing until it reaches the
right place. Unfortunately this process cannot be guaranteed: it may be
the case that the invocation you are looking for has been cut out of the
search space by cuts (
!
) in your program. In this case the
system fails to the latest surviving Call port before the correct one.
- f
-
fail can be used at any of the four ports (although at the Fail
port it has no effect). It transfers control to the Fail port of the
box, forcing the invocation to fail prematurely.
If you supply an integer after the command, then this is taken as
specifying an invocation number and the system tries to get you to the
Fail port of the invocation box you have specified. It does this by
continuously failing until it reaches the right place. Unfortunately
this process cannot be guaranteed: it may be the case that the
invocation you are looking for has been cut out of the search space by
cuts (
!
) in your program. In this case the system fails to the
latest surviving Fail port before the correct one.
- d
-
display goal displays the current goal using
display/1
.
See Write (below).
- p
-
print goal re-prints the current goal.
An argument will override the default printdepth, treating 0 as infinity.
- w
-
write goal writes the current goal using
write/1
.
- g
-
print ancestor goals provides you with a list of ancestors to the
current goal, i.e. all goals that are hierarchically above the current
goal in the calling sequence. You can always be sure of jumping to any
goal in the ancestor list (by using retry etc). If you supply an integer
n, then only that number of ancestors will be printed. That is to
say, the last n ancestors will be printed counting back from the
current goal. Each entry is displayed just as they would be in a trace
message.
- t
-
print backtrace is the same as the above, but also shows any goals
that have exited non-deterministically and their ancestors. This
information shows where there are outstanding choices that the program
could backtrack to. If you supply an integer
n, then only that number of goals will be printed.
Ancestors to the current goal are annotated with the `Call:' port,
as they have not yet exited, whereas goals that have exited are
annotated with the `Exit:' port. The backtrace is a tree rather
than a stack: to find the parent of a given goal with depth indicator
d, look for the closest goal above it with depth indicator
d-1.
- &
-
print blocked goals prints a list of the goals which are currently
blocked in the current debugging session together with the variable that
each such goal is blocked on (see section Procedural Semantics). The goals are
enumerated from 1 and up. If you supply an integer n, then only
that goal will be printed. Each entry is preceded by the goal number
followed by the variable name.
- n
-
nodebug switches the debugger off. Note that this is the correct way
to switch debugging off at a trace point. You cannot use the @ or
b options because they always return to the debugger.
- =
-
debugging outputs information concerning the status of the debugging
package. See section Debugging, the built-in
debugging/0
.
- +
-
spy this sets a spy-point on the current goal. With an
argument, prompts for a condition to be tested each time the debugger
reaches the spy-point. Conditions consist of three parts: a port,
a goal template, and a test (arbitrary Prolog goal), usually
sharing some variables with the test part. For the spy-point to
apply, the port and goal parts must match, and the
test must succeed, otherwise the spy-point is simply disabled. The
following example illustrates how to define a spy-point to only apply at
Call ports when a test is satisfied:
4 2 Call: nr([0,1,2,3,4,5,6,7,8,9,...],_267) ? + 1
Goal, Port, Cond: nr(L,_), call, (length(L,N), N<3).
{Spy-point placed on user:nr/2}
+ 4 2 Call: nr([0,1,2,3,4,5,6,7,8,9,...],_267) ? l
+ 32 30 Call: nr([8,9],_2771) ? s
- -
-
nospy this removes the spy-point from the current goal, if it exists,
and any condition for that spy-point.
- .
-
find this outputs information about where the predicate being called
is defined.
- a
-
abort causes an abort of the current execution. All the execution
states built so far are destroyed and you are put right back at the
top-level. (This is the same as the built-in predicate
abort/0
.)
- b
-
break calls the built-in predicate
break/0
, thus putting you
at a recursive top-level with the execution so far sitting underneath you.
When you end the break (^D) you will be reprompted at the port at
which you broke. The new execution is completely separate from the
suspended one; the invocation numbers will start again from 1 during the
break. The debugger is temporarily switched off as you call the break and
will be re-switched on when you finish the break and go back to the old
execution. However, any changes to the leashing or to spy-points will
remain in effect.
- @
-
command gives you the ability to call arbitrary Prolog goals. It
is effectively a one-off break (see above). The initial message
`| :- ' will be output on the standard error stream, and a command is then read
from the standard input stream and executed as if you were at top level.
- u
-
unify is available at the Call port and gives you the option of
providing a solution to the goal from the standard input stream rather than executing
the goal. This is convenient e.g. for providing a "stub" for a
predicate that has not yet been written. A prompt will be
output on the standard error stream, and the solution is then read from
the standard input stream and unified with the goal.
- e
-
raise exception is available at all ports. A prompt will be
output on the standard error stream, and an exception term is then read from
the standard input stream and raised in the program being debugged.
- <
-
This command, without arguments, resets the printdepth to 10. With an
argument of n, the printdepth is set to n, treating 0 as
infinity.
- ^
-
While at a particular port, a current subterm of the current goal
is maintained. It is the current subterm which is displayed, printed,
or written when prompting for a debugger command. Used in combination
with the printdepth, this provides a means for navigating in the
current goal for focusing on the part which is of interest.
The current subterm is set to the current goal when arriving at a new port.
This command, without arguments, resets the current subterm to the current
goal. With an argument of n (> 0), the current subterm is replaced
by its n:th subterm. With an argument of 0, the current subterm
is replaced by its parent term. With a list of arguments, the arguments are
applied from left to right.
- ?
-
- h
-
help displays the table of options given above.
It is possible, and sometimes useful, to consult a file whilst in the
middle of program execution. Predicates, which have been successfully
executed and are subsequently redefined by a consult and are later
reactivated by backtracking, will not notice the change of their
definitions. In other words, it is as if every predicate, when called,
creates a virtual copy of its definition for backtracking purposes.
If SICStus Prolog is run via the Emacs interface, the commands for
loading code (such as C-c C-p, consulting the current predicate)
are not directly available when the system prompts you after printing
out a debugging message. Press b followed by RET to get a
recursive top-level, ready to accept the Emacs commands. Type ^D
to return to the debugging port.
Usually, exceptions that occur during debugging sessions are displayed
only in trace mode and for invocation boxes for predicates with
spy-points on them, and not during skips. However, it is sometimes
useful to make exceptions trap to the debugger at the earliest
opportunity instead. The following predicate provides such a
possibility
error_exception(+Exception)
-
user:error_exception(+Exception)
-
A hook predicate. This predicate is called at all
exception ports. If it succeeds, the debugger enters trace mode and
prints an exception port message. Otherwise, the debugger mode is
unchanged and a message is printed only in trace mode or if a spy-point
is reached, and not during skips.
Go to the first, previous, next, last section, table of contents.