Node:Accessing Past Debugger States, Next:Storing User Information in the Backtrace, Previous:Built-in Predicates for Breakpoint Handling, Up:Advanced Debugging
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 nondeterministically. 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, then 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 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 which 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, which have exited. These are the
invocations which 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 =< I =< 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 which 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
which
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)
.