[patch] Add an evaluation function hook to Python breakpoints.

Phil Muldoon
2010-12-13T13:50:48Z

This patch allows Python breakpoints to be sub-classed and implements
the "evaluate" function. If the user defines an "evaluate" function in
the Python breakpoint it will be called when GDB evaluates any
conditions assigned to the breakpoint. This allows the user to write
conditional breakpoints entirely in Python, and also allows the user to
collect data at each breakpoint. If the function returns True, GDB will
stop the inferior at the breakpoint; if the function returns False
the inferior will continue.

Tested on x86-64, Fedora 14 with no regressions.

Comments?

Cheers,

Phil

2010-12-13 Phil Muldoon <pmuldoon@redhat.com>

* breakpoint.c (bpstat_check_breakpoint_conditions): Add Python
evaluation code.

2010-12-13 Phil Muldoon <pmuldoon@redhat.com>

* gdb.python/py-breakpoint.exp: Add Python evaluation tests.

2010-12-13 Phil Muldoon <pmuldoon@redhat.com>

* gdb.texinfo (Breakpoints In Python): Add description and example
of Python evaluation function.

--

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 6a51a3b..b9c3d01 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -72,6 +72,11 @@

#include "mi/mi-common.h"

+#if HAVE_PYTHON
+#include "python/python.h"
+#include "python/python-internal.h"
+#endif
+
/* Arguments to pass as context to some catch command handlers. */
#define CATCH_PERMANENT ((void *) (uintptr_t) 0)
#define CATCH_TEMPORARY ((void *) (uintptr_t) 1)
@@ -3964,6 +3969,41 @@ bpstat_check_breakpoint_conditions (bpstat bs, ptid_t ptid)
int value_is_zero = 0;
struct expression *cond;

+ /* Evaluate Python breakpoints that have an "evaluate"
+ function implemented. */
+#if HAVE_PYTHON
+ if (b->py_bp_object)
+ {
+ struct cleanup *cleanup = ensure_python_env (get_current_arch (),
+ current_language);
+ PyObject *gdbpy_bp_eval = PyString_FromString ("evaluate");
+ PyObject *py_bp = (PyObject *) b->py_bp_object;
+
+ if (PyObject_HasAttr (py_bp, gdbpy_bp_eval))
+ {
+ PyObject *result = PyObject_CallMethodObjArgs (py_bp,
+ gdbpy_bp_eval,
+ NULL);
+
+ if (result)
+ {
+ int evaluate = PyObject_IsTrue (result);
+
+ if (evaluate == -1)
+ gdbpy_print_stack ();
+
+ /* If the evaluate function returns False that means the
+ Python breakpoint wants GDB to continue. */
+ if (!evaluate)
+ bs->stop = 0;
+ }
+ else
+ gdbpy_print_stack ();
+ }
+ do_cleanups (cleanup);
+ }
+#endif
+
if (is_watchpoint (b))
cond = b->cond_exp;
else
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index dc9630a..09a02b0 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -22872,7 +22872,33 @@ Return the symbol table's source absolute file name.
@tindex gdb.Breakpoint

Python code can manipulate breakpoints via the @code{gdb.Breakpoint}
-class.
+class. The @code{gdb.Breakpoint} class can be sub-classed and, in
+particular, you may choose to implement the @code{evaluate} function.
+If this function is defined it will be called when the inferior reaches
+that breakpoint. If the @code{evaluate} function returns
+@code{True}, the inferior will be stopped at the location of the
+breakpoint. Otherwise if the function returns @code{False} the
+inferior will continue. The @code{evaluate} function should not
+attempt to influence the state of the inferior (e.g., step) or
+otherwise alter its data.
+
+If there are multiple breakpoints at the same location with an
+@code{evaluate} function, each one will be called regardless of the
+return status of the previous. This ensures that all @code{evaluate}
+functions have a chance to execute at that location. In this scenario
+if one of the functions returns @code{True} but the others return
+@code{False}, the inferior will still be stopped.
+
+Example @code{evaluate} implementation:
+
+@smallexample
+class MyBreakpoint (gdb.Breakpoint):
+ def evaluate (self):
+ inf_val = gdb.parse_and_eval("foo")
+ if inf_val == 3:
+ return True
+ return False
+@end smallexample

@defmethod Breakpoint __init__ spec @r{[}type@r{]} @r{[}wp_class@r{]} @r{[}internal@r{]}
Create a new breakpoint. @var{spec} is a string naming the
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 88d9930..6752d83 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -887,7 +887,7 @@ static PyTypeObject breakpoint_object_type =
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
- Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"GDB breakpoint object", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index 34a64a3..0867acd 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -198,3 +198,67 @@ gdb_py_test_silent_cmd "python wp1 = gdb.Breakpoint (\"result\", type=gdb.BP_WA
gdb_test "info breakpoints" "No breakpoints or watchpoints.*" "Check info breakpoints does not show invisible breakpoints"
gdb_test "maint info breakpoints" ".*hw watchpoint.*result.*" "Check maint info breakpoints shows invisible breakpoints"
gdb_test "continue" ".*\[Ww\]atchpoint.*result.*Old value = 0.*New value = 25.*" "Test watchpoint write"
+
+# Breakpoints that have an evaluation function.
+
+# Start with a fresh gdb.
+clean_restart ${testfile}
+
+if ![runto_main] then {
+ fail "Cannot run to main."
+ return 0
+}
+delete_breakpoints
+
+gdb_py_test_multiple "Sub-class a breakpoint" \
+ "python" "" \
+ "class bp_eval (gdb.Breakpoint):" "" \
+ " inf_i = 0" "" \
+ " count = 0" "" \
+ " def evaluate (self):" "" \
+ " self.count = self.count + 1" "" \
+ " self.inf_i = gdb.parse_and_eval(\"i\")" "" \
+ " if self.inf_i == 3:" "" \
+ " return True" "" \
+ " return False" "" \
+ "end" ""
+
+gdb_py_test_multiple "Sub-class a second breakpoint" \
+ "python" "" \
+ "class bp_also_eval (gdb.Breakpoint):" "" \
+ " count = 0" "" \
+ " def evaluate (self):" "" \
+ " self.count = self.count + 1" "" \
+ " if self.count == 9:" "" \
+ " return True" "" \
+ " return False" "" \
+ "end" ""
+
+set bp_location2 [gdb_get_line_number "Break at multiply."]
+set end_location [gdb_get_line_number "Break at end."]
+gdb_py_test_silent_cmd "python eval_bp1 = bp_eval(\"$bp_location2\")" "Set breakpoint" 0
+gdb_py_test_silent_cmd "python also_eval_bp1 = bp_also_eval(\"$bp_location2\")" "Set breakpoint" 0
+gdb_py_test_silent_cmd "python never_eval_bp1 = bp_also_eval(\"$end_location\")" "Set breakpoint" 0
+gdb_continue_to_breakpoint "Break at multiply." ".*/$srcfile:$bp_location2.*"
+gdb_test "print i" "3" "Check inferior value matches python accounting"
+gdb_test "python print eval_bp1.inf_i" "3" "Check python accounting matches inferior"
+gdb_test "python print also_eval_bp1.count" "4" \
+ "Check non firing same-location breakpoint eval function was also called at each stop."
+gdb_test "python print eval_bp1.count" "4" \
+ "Check non firing same-location breakpoint eval function was also called at each stop."
+
+gdb_py_test_multiple "Sub-class a watchpoint" \
+ "python" "" \
+ "class wp_eval (gdb.Breakpoint):" "" \
+ " def evaluate (self):" "" \
+ " self.result = gdb.parse_and_eval(\"result\")" "" \
+ " if self.result == 788:" "" \
+ " return True" "" \
+ " return False" "" \
+ "end" ""
+
+delete_breakpoints
+gdb_py_test_silent_cmd "python wp1 = wp_eval (\"result\", type=gdb.BP_WATCHPOINT, wp_class=gdb.WP_WRITE)" "Set watchpoint" 0
+gdb_test "continue" ".*\[Ww\]atchpoint.*result.*Old value =.*New value = 788.*" "Test watchpoint write"
+gdb_test "python print never_eval_bp1.count" "0" \
+ "Check that this unrelated breakpoints eval function was never called."