Go to the previous, next section.

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.

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.

@
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.

@
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.

@
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.

@
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.

@
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:

@
Switches the debugger on. (It is initially off.) 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 below), but information prior to this will just be unavailable.

@
Switches the debugger off. If there are any spy-points set then they will be kept but disabled.

@
Prints onto the terminal 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).

Tracing

The following built-in predicate may be used to commence an exhaustive trace of a program.

@
Switches the debugger on, if it is not on already, and ensures that the next time control enters a procedure box, a message will be produced and you will be asked to interact. 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. 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:

@
Leashing Mode is set to Mode. Leashing Mode determines the ports of procedure 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:

@
Prompt on Call.

@
Prompt on Exit.

@
Prompt on Redo.

@
Prompt on Fail.

@
Prompt on Exception.

The initial value of Leashing Mode is [call,exit,redo,fail,exception] (full leashing).

  • notrace Equivalent to nodebug.
  • 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 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:

    @
    Sets spy-points on all the predicates given by Spec, which can have the forms:

    @
    all predicates of that name no matter what arity,

    @
    the predicate of that name and arity,

    @
    the predicates of that name with arity in the range Low-High,

    @
    specifying a particular module instead of the default type-in module, see section Module Prefixing), and

    @
    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 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.

    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 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
    

    @
    @
    creep causes the debugger to single-step to the very next port and print a message. Then if the port is leashed (see section Tracing), 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 terminal.

    @
    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.

    @
    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. There is a way of overriding this : the t option after a ^C interrupt will disable the masking. Normally, however, this masking is just what is required!

    @
    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 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.

    @
    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.

    @
    display goal displays the current goal using display/1. See Write (below).

    @
    print goal re-prints the current goal using print/1. Compound terms will be printed to the default printdepth (below). An argument will override the default, treating 0 as infinity.

    @
    write goal writes the current goal on the terminal using write/1.

    @
    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. It uses the 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 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. The goals are printed using print/1. and each entry is preceded by the goal number followed by the variable name.

    @
    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.

    @
    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.)

    @
    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 your terminal, and a command is then read from the terminal and executed as if you were at top level.

    @
    unify is available at the Call port and gives you the option of providing a solution to the goal from the terminal 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 your terminal, and the solution is then read from the terminal and unified with the goal.

    @
    pending exception is available at the Exception port. It displays the raised exception that caused the transfer to the Exception port.

    @
    While in the debugger, a printdepth is in effect for limiting the subterm nesting level when printing the current goal using 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.

    @
    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.

    @
    @
    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-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.

    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

    @
    @
    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. error_exception/1 is always called in the user module.

    Go to the previous, next section.