7.6.7 Built-in Predicates for Breakpoint Handling

This section introduces built-in predicates for evaluating breakpoint conditions, and for retrieving, deleting, disabling and enabling breakpoints.

The breakpoint spec of the last advice-point example was quite complex. And, to be practical, it should be improved to assert only line numbers not recorded so far. For this you will write a Prolog predicate for the conditional assertion of file/line information, assert_line_reached(File,Line), and use it instead of the assert(line_reached(F,L)) condition.

Because of the complexity of the breakpoint spec, it looks like a good idea to move the if-then-else condition into Prolog code. This requires that we test the line(F,L) condition from Prolog. The built-in predicate execution_state/1 serves for this purpose. It takes a simple or a composite breakpoint condition as its argument and evaluates it, as if in the test part of a breakpoint spec. The predicate will succeed iff the breakpoint condition evaluates successfully. Thus execution_state/1 allows you to access debugging information from within Prolog code. For example, you can write a Prolog predicate, assert_line_reached/0, which queries the debugger for the current line information and then processes the line number:

     assert_line_reached :-
             (   execution_state(line(F,L)) -> assert_line_reached(F,L).
             ;   true
             ).
     
     | ?- add_breakpoint([advice,call]-[true(assert_line_reached),flit], _).

Arbitrary tests can be used in execution_state/1, if it is called from within a true condition. It can also be called from outside the debugger, but then only a subset of conditions is available. Furthermore, the built-in predicate execution_state/2 allows accessing information from past debugger states (see Accessing Past Debugger States).

The built-in predicates remove_breakpoints(BIDs), disable_breakpoints(BIDs) and enable_breakpoints(BIDs) serve for removing, disabling and enabling the given breakpoints. Here BIDs can be a single breakpoint identifier, a list of these, or one of the atoms all, advice, debugger.

We now show an application of remove_breakpoints/1 for implementing one-off breakpoints, i.e. breakpoints that are removed when first activated.

For this we need to get hold of the currently selected breakpoint identifier. The bid(BID) condition serves for this purpose: it unifies its argument with the identifier of the breakpoint being processed. The following is an example of a one-off breakpoint.

     | ?- spy(foo/2, -[bid(BID),true(remove_breakpoints(BID)),leash]).
     % Conditional spypoint for user:foo/2 added, BID=1
     % zip
     | ?- foo(2, X).
     % Conditional spypoint for user:foo/2, BID=1, removed (last)
             1      1 Call: foo(2,_402) ? z
     X = 1

The action part of the above breakpoint calls the bid test to obtain the breakpoint identifier. It then uses this number as the argument to the built-in predicate remove_breakpoints/1, which removes the activated breakpoint.

The built-in predicate current_breakpoint(Spec, BID, Status, Kind, Type) enumerates all breakpoints present in the debugger. For example, if we call current_breakpoint/5 before the invocation of foo/2 in the last example, we get this:

     | ?- current_breakpoint(Spec, BID, Status, Kind, Type).
     Spec = [pred(user:foo/2)]-[bid(_A),true(remove_breakpoints(_A)),leash],
     BID = 1,
     Status = on,
     Kind = conditional(user:foo/2),
     Type = debugger

Here Spec is the breakpoint spec of the breakpoint with identifier BID. Status is on for enabled breakpoints and off for disabled ones. Kind is one of plain(MFunc), conditional(MFunc) or generic, where MFunc is the module qualified functor of the specific breakpoint. Finally Type is the breakpoint type: debugger or advice.

The Spec returned by current_breakpoint/5 is exactly the same as the one given in add_breakpoint/2. If the breakpoint was created by spy/2, then the test part is extended by a pred condition, as exemplified above. Earlier we described some pre-processing steps that the spec goes through, such as moving the module qualification of the spec to certain conditions. These transformations are performed on the copy of the breakpoint used for testing. Independently of this, the debugger also stores the original breakpoint, which is returned by current_breakpoint/5.