Node:Advice-points, Next:Built-in Predicates for Breakpoint Handling, Previous:Breakpoint Actions, Up:Advanced Debugging
As mentioned earlier, there are two kinds of breakpoints: spypoints and
advice-points. The main purpose of spypoints is to support interactive
debugging. In contrast with this, advice-points can help you to
perform non-interactive debugging activities. For example, the following
advice-point will check a program invariant: whether the condition
Y-X<3
always holds at exit from foo(X,Y)
.
| ?- add_breakpoint([pred(foo/2),advice] -[exit,goal(foo(X,Y)),\+true(Y-X<3),trace], _). % Conditional advice point for user:foo/2 added, BID=1 true ? yes % advice | ?- foo(4, Y). Y = 3 ? yes % advice | ?- foo(9, Y). 3 3 Exit: foo(7,13) ? n 2 2 Exit: foo(8,21) ?
The test part of the above breakpoint contains a pred
test, and
the advice
condition, making it an advice-point. (You can also
include the debugger
condition in spypoint specs, although this
is the default interpretation.)
The action part starts with the exit
port condition. Because of
this the rest of the action part is evaluated only at Exit ports. By
placing the port condition in the action part, we ensure the creation of
a procedure box at the Call port, as explained earlier.
Next, we get hold of the goal arguments using the goal
condition,
and use the \+true(Y-X<3)
test to check if the invariant is
violated. If this happens, the last condition sets the mode
action variable to trace
, switching on the interactive debugger.
Following the add_breakpoint/2
call the above example shows two
top-level calls to foo/2
. The invariant holds within the first
goal, but is violated within the second. Notice that the advice
mechanism works with the interactive debugger switched off.
You can ask the question, why do we need advice-points? The same task could be implemented using a spypoint. For example:
| ?- add_breakpoint(pred(foo/2) -[exit,goal(foo(X,Y)),\+true(Y-X<3),leash], _). % The debugger will first zip -- showing spypoints (zip) % Conditional spypoint for user:foo/2 added, BID=1 true ? yes % zip | ?- foo(4, X). X = 3 ? yes % zip | ?- foo(9, X). * 3 3 Exit: foo(7,13) ? z * 2 2 Exit: foo(8,21) ?
The main reason to have a separate advice mechanism is to be able to perform checks independently of the interactive debugging. With the second solution, if you happen to start some interactive debugging, you cannot be sure that the invariant is always checked. For example, no spypoints will be activated during a skip. In contrast with this, the advice mechanism is watching the program execution all the time, independently of the debugging mode.
Advice-points are handled in very much the same way as spypoints are. When arriving at a port, advice-point selection takes place first, followed by spypoint selection. This can be viewed as the debugger making two passes over the current breakpoints, considering advice-points only in the first pass, and spypoints only in the second.
In both passes the debugger tries to find a breakpoint which can be activated, checking the test and action parts, as described earlier. However, there are some differences between the two passes:
mode
is set to current debugging mode, command = proceed
,
show = silent
. Note that this is done independently of the
debugging mode (in contrast with the spypoint search initialization).
[]
. This means that
if no action part is given, then the only effect of the advice-point
will be to build a procedure box (because of the command = proceed
initialization).
command
is set to
flit
.
Having performed advice processing, the debugger inspects the
command
variable. The command values different from
proceed
and flit
are called divertive, as they alter the
normal flow of control (e.g. proceed(...,...)
), or involve user
interaction (ask
). If the command
value is divertive, then
the prescribed action is performed immediately, without executing the
spypoint selection process. Otherwise, if command = proceed
, it
is noted that the advice part requests the building of a procedure box.
Next, the second, spypoint processing pass is carried out, and possible
user interaction takes place, as described earlier. A procedure box is
built if either the advice-point or the spypoint search requests this.
Let us conclude this section by another example, a generic advice point for collecting branch coverage information:
| ?- add_breakpoint( (advice,call) - ( line(F,L) -> true(assert(line_reached(F,L))), flit ; flit ), _). % Generic advice point added, BID=1 true ? yes % advice,source_info | ?- foo(4,X). X = 3 ? ; no % advice,source_info | ?- setof(X, line_reached(F,X), S). F = '/home/bob/myprog.pl', S = [31,33,34,35,36] ?
This advice-point will be applicable at every Call port. It will then
assert a fact with the file name and the line number if source
information is available. Finally, it will set the command
variable to flit
on both branches of execution. This is to
communicate the fact that the advice-point does not request the building
of a procedure box.
It is important to note that this recording of the line numbers reached is performed independently of the interactive debugging.
In this example we used the ','/2
operator, rather than list
notation, for describing the conjunction of conditions, as this seems to
fit better the if-then-else expression used in the action part. We could
have still used lists in the tests part, and in the "then" part of the
actions. Note that if we omit the "else" branch, the action part will
fail if no source information is available for the given call. This
will cause a procedure box to be built, which is an unnecessary
overhead. An alternative solution, using the line/2
test twice,
is the following:
| ?- add_breakpoint([advice,call,line(_,_)]- [line(F,L),true(assert(line_reached(F,L))),flit], _).
Further examples of advice-points are available in
library(debugger_examples)
.