In this section we introduce the built-in predicates for accessing past debugger states, and the breakpoint conditions related to these.
The debugger collects control flow information about the goals being executed, more precisely about those goals, for which a procedure box is built. This collection of information, the backtrace, includes the invocations that were called but not exited yet, as well as those that exited nondeterminately. For each invocation, the main data items present in the backtrace are the following: the goal, the module, the invocation number, the depth and the source information, if any.
Furthermore, as you can enter a new break level from within the debugger, there can be multiple backtraces, one for each active break level.
You can access all the information collected by the debugger using the
built-in predicate execution_state(
Focus,
Tests)
. Here Focus is a ground term specifying
which break level and which invocation to access. It can be one of the
following:
break_level(
BL)
selects the current invocation
within the break level BL.
inv(
Inv)
selects the invocation number Inv within the
current break level.
Note that the top-level counts as break level 0, while the invocations are numbered from 1 upwards.
The second argument of execution_state/2
, Tests, is a
simple or composite breakpoint condition. Most simple tests can
appear inside Tests, with the exception of the port
,
bid
, advice
, debugger
, and get
tests. These
tests will be interpreted in the context of the specified past debugger
state. Specifically, if a true/1
condition is used, any
execution_state/1
queries appearing in it will be evaluated in
the past context.
To illustrate the use of execution_state/2
, we now define a
predicate last_call_arg(ArgNo, Arg)
, which is to be called
from within a break, and which will look at the last debugged goal
of the previous break level, and return in Arg
the ArgNo
th
argument of this goal.
last_call_arg(ArgNo, Arg) :- execution_state(break_level(BL1)), BL is BL1-1, execution_state(break_level(BL), goal(Goal)), arg(ArgNo, Goal, Arg).
We see two occurrences of the term break_level(...)
in
the above example. Although these look very similar, they have different
roles. The first one, in execution_state/1
, is a breakpoint
test, which unifies the current break level with its
argument. Here it is used to obtain the current break level and
store it in BL1
. The second use of break_level(...)
,
in the first argument of execution_state/2
, is a focus
condition, whose argument has to be instantiated, and which
prescribes the break level to focus on. Here we use it to obtain the
goal of the current invocation of the previous break level.
Note that the goal retrieved from the backtrace is always in its latest instantiation state. For example, it is not possible to get hold of the goal instantiation at the Call port, if the invocation in question is at the Exit port.
Here is an example run, showing how last_call_arg/2
can be used:
5 2 Call: _937 is 13+8 ? b % Break level 1 % 1 | ?- last_call_arg(2, A). A = 13+8
There are some further breakpoint tests that are primarily used in looking at past execution states.
The test max_inv(
MaxInv)
returns the maximal invocation
number within the current (or selected) break level. The test
exited(
Boolean)
unifies Boolean with
true
if the invocation has exited, and with false
otherwise.
The following example predicate lists those goals in the
backtrace, together with their invocation numbers, that have
exited. These are the invocations that are listed by the t
interactive debugger command (print backtrace), but not by the g
command (print ancestor goals). Note that the predicate
between(N,M,I)
enumerates all integers such that N
\leq I \leq M.
exited_goals :- execution_state(max_inv(Max)), between(1, Max, Inv), execution_state(inv(Inv), [exited(true),goal(G)]), format('~t~d~6| ~p\n', [Inv,G]), fail. exited_goals. (...) ?* 41 11 Exit: foo(2,1) ? @ | :- exited_goals. 26 foo(3,2) 28 bar(3,1,1) 31 foo(2,1) 33 bar(2,1,0) 36 foo(1,1) 37 foo(0,0) 39 foo(1,1) 41 foo(2,1) 43 bar(2,1,0) 46 foo(1,1) 47 foo(0,0) ?* 41 11 Exit: foo(2,1) ?
Note that similar output can be obtained by entering a new break level
and calling exited_goals
from within an execution_state/2
:
% 1 | ?- execution_state(break_level(0), true(exited_goals)).
The remaining two breakpoint tests allow you to find parent
and ancestor invocations in the backtrace. The
parent_inv(
Inv)
test unifies Inv with the
invocation number of the youngest ancestor present in the
backtrace, called debugger-parent for short. The test
ancestor(
AncGoal,
Inv)
looks for the youngest
ancestor in the backtrace that is an instance of
AncGoal. It then unifies the ancestor goal with
AncGoal and its invocation number with Inv.
Assume you would like to stop at all invocations of foo/2
that
are somewhere within bar/1
, possibly deeply nested. The following
two breakpoints achieve this effect:
| ?- spy(bar/1, advice), spy(foo/2, ancestor(bar(_),_)). % Plain advice point for user:bar/1 added, BID=3 % Conditional spypoint for user:foo/2 added, BID=4
We added an advice-point for bar/1
to ensure that all calls
to it will have procedure boxes built, and so become part of the
backtrace. Advice-points are a better choice than
spypoints for this purpose, as with ?- spy(bar/1, -proceed)
the debugger will not stop at the call port of bar/1
in
trace mode. Note that it is perfectly all right to create an
advice-point using spy/2
, although this is a bit of
terminological inconsistency.
Further examples of accessing past debugger states can be found in
library(debugger_examples)
.