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


The interactive debugger

The Ciao program development environment includes a number of advanced debugging tools, such as the ciaopp preprocessor and some execution visualizers. Herein we discuss the interactive debugger available in the standard top-level, which allows to trace the control flow of programs, similar to other popular Prolog systems. This is a classical Byrd 'box-type' debugger [Byr80,BBP81], with some enhancements.

Byrd's Procedure Box model of debugging execution provides a simple way of visualising control flow, especially during backtracking. Control flow is viewed at the predicate level, rather than at the level of individual clauses. The Ciao debugger has the ability to mark selected modules and/or files for debugging, rather than having to exhaustively trace your program. It also allows to selectively set spy-points. Spy-points allow the programmer to nominate interesting predicates at which program execution is to pause so that the programmer can interact with the debugger. There is a wide choice of control and information options available during debugging interaction.

Marking modules and files for debugging

Usually, when a program is not working properly, the programmer has a feeling of which are the modules where the fault may be. Since full-fledged debugging is only available on consulted (interpreted) modules, which are executed much slower than compiled modules, there is the posibility of telling the top level which particular modules are wanted to be loaded in consult mode, with the aim of debugging them. The most simple way of achieving this (appart from using the Ciao emacs mode, which includes special support for debugging) is executing in the Ciao shell prompt, for each suspicious module Module in the program, a command like this:

?- debug_module(Module).

Note that files without module declaration belong to the module user, so the command to be issued for debugging a user file, say 'foo.pl', would be debug_module(user), and not debug_module(foo).

The above command performs in fact two related things: on the one hand, it instructs the compiler to load any file containing a module of this name in consult mode; on the other, it instructs the debugger to actually prepare for debugging the code belonging to that module. After that, the modules which are to be debugged have to be (re)loaded in any appropriate form. The nice thing is that, if the modules are part of a bigger application, a command to load the main application file will automatically force loading the dependent modules which have changed, including those whose loading mode has changed, thus allowing the debugging of required files.

Later, as the bug location is isolated, one would want to restrict more and more the modules where debugging takes place. To this end, and without the need of reloading, one can tell the debugger to not consider a module for debugging issuing a nodebug_module/1 command, which counteracts a debug_module/1 command with the same module name.

There exist also the top-level commands set_debug_mode/1 and set_nodebug_mode/1, which accept as argument a file spec (i.e., library(foo) or foo, even if it is a user file) to be able to load a file in consult mode without changing the set of modules the debugger will try to spy.

The debugging process

Once modules or user files are marked for debugging and reloaded, the traditional debugging shell commands can be used (the debugger library following this section contains all the commands and their description), with the same meaning as in other Prolog systems such as SICStus. The difference in their behavior is that in Ciao debugging takes place only in the modules in which it was activated, and that nospy/1 and spy/1 accept sequences of predicate specs, and they will search for those predicates only in the debugging-marked modules.

The procedure box control flow model

During debugging the interpreter 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 interpreter prints out goals. As in other programming languages, key points of interest are procedure 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 the interpreter, and enables the user to view the behaviour of the program in a consistent way.

Let us look at an example Prolog procedure:

Image:autofigbyrdbox.jpg

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 procedure and labelled arrows indicate the control flow in and out of this box. There are four such arrows which we shall look at in turn.

In terms of this model, the information we get about the procedure box is only the control flow through these four 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 around the procedure should really be seen as an invocation box. That is, there will be a different box for each different invocation of the procedure. Obviously, with something like a recursive procedure, 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.

Format of debugging messages

This section explains the exact format of the message output by the debugger at a port. All trace messages are output to the terminal regardless of where the current output stream is directed (which allows tracing programs while they are performing file IO). The basic format is as follows:

S  13  7  Call: T user:descendant(dani,_123) ?

S is a spy-point indicator. It is printed as '+', indicating that there is a spy-point on descendant/2 in module user, or as ' ', 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 always nondecreasing (provided that the debugger is switched on) regardless of whether or not the invocations are being actually seen. 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 or Fail). The goal is then printed so that its current instantiation state can be inspected. The final ? is the prompt indicating that the debugger is waiting for user interaction. One of the option codes allowed (see below) can be input at this point.

Ports can be unleashed. This means that the debugger will not stop for interaction at the messages for an unleashed port. Obviously, the ? prompt will not be shown in such messages, since the user has specified to 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, basic control structures, and some builtins. This means that messages will never be printed concerning these predicates during debugging.

Options available during debugging

This section describes the particular options that are available when the debugger prompts 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 next terminator (carriage-return, line-feed, or escape). Some options only actually require the terminator; e.g., the creep option, only requires RET.

The only option which do really needs to be remembered is 'h' (followed by RET). This provides help in the form of the following list of available options.

<cr>   creep            c      creep
 l     leap             s      skip
 r     retry            r <i>  retry i
 f     fail             f <i>  fail i
 d     display          p      print
 w     write
 g     ancestors        g <n>  ancestors n
 n     nodebug          =      debugging
 +     spy this         -      nospy this
 a     abort            b      break
 @     command          u      unify
 <     reset printdepth < <n>  set printdepth
 ^     reset subterm    ^ <n>  set subterm
 ?     help             h      help


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