Node:Accessing Past Debugger States, Next:Storing User Information in the Backtrace, Previous:Built-in Predicates for Breakpoint Handling, Up:Advanced Debugging
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, you can enter a new break level from within the debugger, so 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
tests and the action variable queries.
The tests appearing in the second argument 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 8-2 ? b % Break level 1 % 1 | ?- last_call_arg(2, A). A = 8-2 ?
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
last_port(
LastPort)
can be used for obtaining approximate
port information for invocations in the backtrace: it unifies
LastPort port with exit(nondet)
if the invocation has
exited, and with call
otherwise. The latter piece of information
is inaccurate: all we know is that the control is still within the
invocation in question, but the last port passed through may have been
e.g. the Redo port.
The following sample predicate lists those goals in the backtrace,
together with their invocation numbers, which have exited
nondeterministically. (Predicate between(N, M, I)
enumerates all
integers such that N =< I =< M
.)
nondet_goals :- execution_state(max_inv(Max)), between(1, Max, Inv), execution_state(inv(Inv), [last_port(exit(nondet)),goal(G)]), format('~t~d~6| ~p\n', [Inv,G]), fail. nondet_goals. ?* 27 2 Exit: foo(2,1) ? @ | :- nondet_goals. 6 foo(3,2) 11 foo(2,1) 16 foo(1,1) 19 foo(0,0) 23 foo(1,1) 32 foo(1,1) 35 foo(0,0) ?* 27 2 Exit: foo(2,1) ?
Note that similar output can be obtained by entering a new break level
and calling nondet_goals
from within an execution_state/2
:
% 1 | ?- execution_state(break_level(0), true(nondet_goals)).
The remaining two breakpoint tests allow you to find parent and ancestor
invocations in the backtrace. The test parent_inv(
Inv)
unifies Inv with the invocation number of the youngest ancestor
present in the backtrace, called debugger parent for
short. Similarly, the test ancestor(
AncGoal,
Inv)
looks for the youngest ancestor in the backtrace which is unifiable with
AncGoal, and returns its invocation number in Inv.
Assume you would like to stop at all invocations of foo/2
which
are within bar/1
. 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
?- spy(bar/1, -proceed)
will unleash bar/1
when run 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)
.