Go to the previous, next section.
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 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.
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.
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.
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:
The following built-in predicate may be used to commence an exhaustive trace 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. 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:
The initial value of Leashing Mode is
[call,exit,redo,fail,exception]
(full leashing).
nodebug
.
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 procedure 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:
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].
spy Spec
except that all the predicates given
by Spec will have previously set spy-points removed from them.
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 terminal regardless of where the current output stream is directed. (This allows you to trace programs while they are performing file IO.) The basic format is as follows:
S 23 6 Call: T foo(hello,there,_123) ?
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.
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 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).
The goal is then printed so that you can inspect its current instantiation
state. This is done using print/1
(see section Input and Output of Terms) so that all goals
output by the tracing mechanism can be pretty printed if the user
desires.
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 not all procedure calls are traced; there are a few basic
predicates which have been made invisible since it is more convenient
not to trace them. These include debugging directives and basic control
structures, including trace/0
, debug/0
, notrace/0
,
nodebug/0
, spy/1
, nospy/1
, nospyall/0
,
leash/1
, debugging/0
, true/0
, !/0
,
','/2
, '->'/2
, ;/2
, '\+'/1
, and if/3
.
This means that you will never see messages concerning these predicates
during debugging.
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 terminal 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 s skip 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 & 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 pending exception < reset printdepth < <n> set printdepth ^ reset subterm ^ <n> set subterm ? help h help
If you supply an integer after the retry command, then this is taken as
specifying an invocation number and the system tries to get you to the
Call port, not of the current box, but 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 Call port before the correct one.
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.
display/1
.
See Write (below).
print/1
.
Compound terms will be printed to the default printdepth (below).
An argument will override the default, treating 0 as infinity.
write/1
.
ancestors/1
built-in
predicate (see section Information about the State of the Program). 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. The list is printed using print/1
and each entry is
preceded by the invocation number followed by the depth number (as would be
given in a trace message).
print/1
.
and each entry is preceded by the goal number followed by the variable
name.
debugging/0
.
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
abort/0
.)
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.
print/1
.
When displaying or writing the current goal, all nesting levels are shown.
The limit is initially 10. This command, without arguments, resets the
limit to 10. With an argument of n, the limit is set to n,
treating 0 as infinity.
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-c, consult-region
) 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/1
is always
called in the user
module.
Go to the previous, next section.