Go to the first, previous, next, last section, table of contents.


Debugging

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.

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.

The Procedure Box Control Flow Model

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.

Basic Debugging Predicates

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:
  1. Whether undefined predicates are being trapped.
  2. Whether the debugger is switched on.
  3. What spy-points have been set (see below).
  4. What mode of leashing is in force (see below).

Spy-points

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.

Format of Debugging messages

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.

Options Available during Debugging

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.

Consulting during Debugging

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.

Catching Exceptions

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.