2011-03-14 Phil Muldoon <pmuldoon@redhat.com>
authorPhil Muldoon <pmuldoon@redhat.com>
Mon, 14 Mar 2011 16:09:55 +0000 (16:09 +0000)
committerPhil Muldoon <pmuldoon@redhat.com>
Mon, 14 Mar 2011 16:09:55 +0000 (16:09 +0000)
    * gdb.texinfo (Breakpoints In Python): Add description and
              example
      of Python stop function operation.

2010-03-14  Phil Muldoon  <pmuldoon@redhat.com>

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

2011-03-14  Phil Muldoon  <pmuldoon@redhat.com>

    * python/python.h: Declare gdbpy_should_stop and
    gdbpy_breakpoint_has_py_cond.
    * python/python.c: Add python.h to includes.  Remove
    python.h from
    HAVE_PYTHON definition
    (gdbpy_should_stop): New dummy function.
    (gdbpy_breakpoint_has_py_cond): New dummy function.
    * python/py-breakpoint.c (bppy_init): Rewrite to allow
    sub-classing capabilities.
    (gdbpy_should_stop): New function.
    (gdbpy_breakpoint_has_py_cond): New function.
    (local_setattro): New function.
    * breakpoint.c (condition_command): Add check for Python
    'stop'
    operation.
    (bpstat_check_breakpoint_conditions): Execute Python
    'stop'
    operation function as part of stop/continue tests.

gdb/ChangeLog
gdb/breakpoint.c
gdb/doc/ChangeLog
gdb/doc/gdb.texinfo
gdb/python/py-breakpoint.c
gdb/python/python.c
gdb/python/python.h
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.python/py-breakpoint.exp

index 19294a7ab324dbb0d19a0fff30943a2df93fa157..4662422dd1410e41427bf261b09cae21a61c6b85 100644 (file)
@@ -1,3 +1,21 @@
+2011-03-14  Phil Muldoon  <pmuldoon@redhat.com>
+
+       * python/python.h: Declare gdbpy_should_stop and
+       gdbpy_breakpoint_has_py_cond.
+       * python/python.c: Add python.h to includes.  Remove python.h from
+       HAVE_PYTHON definition
+       (gdbpy_should_stop): New dummy function.
+       (gdbpy_breakpoint_has_py_cond): New dummy function.
+       * python/py-breakpoint.c (bppy_init): Rewrite to allow
+       sub-classing capabilities.
+       (gdbpy_should_stop): New function.
+       (gdbpy_breakpoint_has_py_cond): New function.
+       (local_setattro): New function.
+       * breakpoint.c (condition_command): Add check for Python 'stop'
+       operation.
+       (bpstat_check_breakpoint_conditions): Execute Python 'stop'
+       operation function as part of stop/continue tests.
+
 2011-03-14  Tom Tromey  <tromey@redhat.com>
 
        PR gdb/12576:
index d9e5b1e02ef9bd2c3b8bb0b8633beea318974268..c55e5c06571dec3d246da2d27a7f787e35beb594 100644 (file)
@@ -72,6 +72,7 @@
 #undef savestring
 
 #include "mi/mi-common.h"
+#include "python/python.h"
 
 /* Arguments to pass as context to some catch command handlers.  */
 #define CATCH_PERMANENT ((void *) (uintptr_t) 0)
@@ -643,6 +644,14 @@ condition_command (char *arg, int from_tty)
   ALL_BREAKPOINTS (b)
     if (b->number == bnum)
       {
+       /* Check if this breakpoint has a Python object assigned to
+          it, and if it has a definition of the "stop"
+          method.  This method and conditions entered into GDB from
+          the CLI are mutually exclusive.  */
+       if (b->py_bp_object
+           && gdbpy_breakpoint_has_py_cond (b->py_bp_object))
+         error (_("Cannot set a condition where a Python 'stop' "
+                  "method has been defined in the breakpoint."));
        set_breakpoint_condition (b, p, from_tty);
        return;
       }
@@ -4070,6 +4079,11 @@ bpstat_check_breakpoint_conditions (bpstat bs, ptid_t ptid)
       int value_is_zero = 0;
       struct expression *cond;
 
+      /* Evaluate Python breakpoints that have a "stop"
+        method implemented.  */
+      if (b->py_bp_object)
+       bs->stop = gdbpy_should_stop (b->py_bp_object);
+
       if (is_watchpoint (b))
        cond = b->cond_exp;
       else
index 8edb4b7e4ffa1d4c59036bd009d8c43e91099861..f0fa2ca65a2d974105bc4cc6f5238d812a715500 100644 (file)
@@ -1,3 +1,9 @@
+2011-03-14  Phil Muldoon  <pmuldoon@redhat.com>
+
+       * gdb.texinfo (Breakpoints In Python): Add description and example
+       of Python stop function operation.
+
+
 2011-03-10  Phil Muldoon  <pmuldoon@redhat.com>
 
        * gdb.texinfo (Parameters In Python): Document get_set_string and
index 4c07af9fa738c23136e9e682bbced8c43568e110..01455d1e0960d52ea9a82e81dff475ab67cd35f4 100644 (file)
@@ -23115,6 +23115,34 @@ argument defines the class of watchpoint to create, if @var{type} is
 assumed to be a @var{WP_WRITE} class.
 @end defmethod
 
+@defop Operation {gdb.Breakpoint} stop (self)
+The @code{gdb.Breakpoint} class can be sub-classed and, in
+particular, you may choose to implement the @code{stop} method.
+If this method is defined as a sub-class of @code{gdb.Breakpoint},
+it will be called when the inferior reaches any location of a
+breakpoint which instantiates that sub-class.  If the method returns
+@code{True}, the inferior will be stopped at the location of the
+breakpoint, otherwise the inferior will continue.
+
+If there are multiple breakpoints at the same location with a
+@code{stop} method, each one will be called regardless of the
+return status of the previous.  This ensures that all @code{stop}
+methods have a chance to execute at that location.  In this scenario
+if one of the methods returns @code{True} but the others return
+@code{False}, the inferior will still be stopped.
+
+Example @code{stop} implementation:
+
+@smallexample
+class MyBreakpoint (gdb.Breakpoint):
+      def stop (self):
+        inf_val = gdb.parse_and_eval("foo")
+        if inf_val == 3:
+          return True
+        return False
+@end smallexample
+@end defop
+
 The available watchpoint types represented by constants are defined in the
 @code{gdb} module:
 
index bfebb1a2f0469812832266d4805ad648196c36b9..0c21bfc3026839e61209d48e49e9025fa45c8275 100644 (file)
@@ -28,6 +28,8 @@
 #include "observer.h"
 #include "cli/cli-script.h"
 #include "ada-lang.h"
+#include "arch-utils.h"
+#include "language.h"
 
 static PyTypeObject breakpoint_object_type;
 
@@ -38,6 +40,9 @@ static int bppy_live;
    constructor and the breakpoint-created hook function.  */
 static breakpoint_object *bppy_pending_object;
 
+/* Function that is called when a Python condition is evaluated.  */
+static char * const stop_func = "stop";
+
 struct breakpoint_object
 {
   PyObject_HEAD
@@ -590,10 +595,9 @@ bppy_get_ignore_count (PyObject *self, void *closure)
 }
 
 /* Python function to create a new breakpoint.  */
-static PyObject *
-bppy_new (PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
+static int
+bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 {
-  PyObject *result;
   static char *keywords[] = { "spec", "type", "wp_class", "internal", NULL };
   char *spec;
   int type = bp_breakpoint;
@@ -604,19 +608,16 @@ bppy_new (PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
 
   if (! PyArg_ParseTupleAndKeywords (args, kwargs, "s|iiO", keywords,
                                     &spec, &type, &access_type, &internal))
-    return NULL;
+    return -1;
 
   if (internal)
     {
       internal_bp = PyObject_IsTrue (internal);
       if (internal_bp == -1)
-       return NULL;
+       return -1;
     }
 
-  result = subtype->tp_alloc (subtype, 0);
-  if (! result)
-    return NULL;
-  bppy_pending_object = (breakpoint_object *) result;
+  bppy_pending_object = (breakpoint_object *) self;
   bppy_pending_object->number = -1;
   bppy_pending_object->bp = NULL;
   
@@ -653,14 +654,14 @@ bppy_new (PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
     }
   if (except.reason < 0)
     {
-      subtype->tp_free (result);
-      return PyErr_Format (except.reason == RETURN_QUIT
-                          ? PyExc_KeyboardInterrupt : PyExc_RuntimeError,
-                          "%s", except.message);
+      PyErr_Format (except.reason == RETURN_QUIT
+                   ? PyExc_KeyboardInterrupt : PyExc_RuntimeError,
+                   "%s", except.message);
+      return -1;
     }
 
-  BPPY_REQUIRE_VALID ((breakpoint_object *) result);
-  return result;
+  BPPY_SET_REQUIRE_VALID ((breakpoint_object *) self);
+  return 0;
 }
 
 \f
@@ -711,6 +712,69 @@ gdbpy_breakpoints (PyObject *self, PyObject *args)
   return PyList_AsTuple (list);
 }
 
+/* Call the "stop" method (if implemented) in the breakpoint
+   class.  If the method returns True, the inferior  will be
+   stopped at the breakpoint.  Otherwise the inferior will be
+   allowed to continue.  */
+
+int
+gdbpy_should_stop (struct breakpoint_object *bp_obj)
+{
+  int stop = 1;
+
+  PyObject *py_bp = (PyObject *) bp_obj;
+  struct breakpoint *b = bp_obj->bp;
+  struct gdbarch *garch = b->gdbarch ? b->gdbarch : get_current_arch ();
+  struct cleanup *cleanup = ensure_python_env (garch, current_language);
+
+  if (PyObject_HasAttrString (py_bp, stop_func))
+    {
+      PyObject *result = PyObject_CallMethod (py_bp, stop_func, NULL);
+
+      if (result)
+       {
+         int evaluate = PyObject_IsTrue (result);
+
+         if (evaluate == -1)
+           gdbpy_print_stack ();
+
+         /* If the "stop" function returns False that means
+            the Python breakpoint wants GDB to continue.  */
+         if (! evaluate)
+           stop = 0;
+
+         Py_DECREF (result);
+       }
+      else
+       gdbpy_print_stack ();
+    }
+  do_cleanups (cleanup);
+
+  return stop;
+}
+
+/* Checks if the  "stop" method exists in this breakpoint.
+   Used by condition_command to ensure mutual exclusion of breakpoint
+   conditions.  */
+
+int
+gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj)
+{
+  int has_func = 0;
+  PyObject *py_bp = (PyObject *) bp_obj;
+  struct gdbarch *garch = bp_obj->bp->gdbarch ? bp_obj->bp->gdbarch :
+    get_current_arch ();
+  struct cleanup *cleanup = ensure_python_env (garch, current_language);
+  
+  if (py_bp == NULL)
+    return 0;
+
+  has_func = PyObject_HasAttrString (py_bp, stop_func);
+  do_cleanups (cleanup);
+
+  return has_func;
+}
+
 \f
 
 /* Event callback functions.  */
@@ -797,7 +861,6 @@ gdbpy_initialize_breakpoints (void)
 {
   int i;
 
-  breakpoint_object_type.tp_new = bppy_new;
   if (PyType_Ready (&breakpoint_object_type) < 0)
     return;
 
@@ -832,6 +895,37 @@ gdbpy_initialize_breakpoints (void)
 
 \f
 
+/* Helper function that overrides this Python object's
+   PyObject_GenericSetAttr to allow extra validation of the attribute
+   being set.  */
+
+static int 
+local_setattro (PyObject *self, PyObject *name, PyObject *v)
+{
+  breakpoint_object *obj = (breakpoint_object *) self;  
+  char *attr = python_string_to_host_string (name);
+  
+  if (attr == NULL)
+    return -1;
+  
+  /* If the attribute trying to be set is the "stop" method,
+     but we already have a condition set in the CLI, disallow this
+     operation.  */
+  if (strcmp (attr, stop_func) == 0 && obj->bp->cond_string)
+    {
+      xfree (attr);
+      PyErr_SetString (PyExc_RuntimeError, 
+                      _("Cannot set 'stop' method.  There is an " \
+                        "existing GDB condition attached to the " \
+                        "breakpoint."));
+      return -1;
+    }
+  
+  xfree (attr);
+  
+  return PyObject_GenericSetAttr ((PyObject *)self, name, v);  
+}
+
 static PyGetSetDef breakpoint_object_getset[] = {
   { "enabled", bppy_get_enabled, bppy_set_enabled,
     "Boolean telling whether the breakpoint is enabled.", NULL },
@@ -901,9 +995,9 @@ static PyTypeObject breakpoint_object_type =
   0,                             /*tp_call*/
   0,                             /*tp_str*/
   0,                             /*tp_getattro*/
-  0,                             /*tp_setattro*/
+  (setattrofunc)local_setattro,   /*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 */
@@ -913,5 +1007,13 @@ static PyTypeObject breakpoint_object_type =
   0,                             /* tp_iternext */
   breakpoint_object_methods,     /* tp_methods */
   0,                             /* tp_members */
-  breakpoint_object_getset       /* tp_getset */
+  breakpoint_object_getset,      /* tp_getset */
+  0,                             /* tp_base */
+  0,                             /* tp_dict */
+  0,                             /* tp_descr_get */
+  0,                             /* tp_descr_set */
+  0,                             /* tp_dictoffset */
+  bppy_init,                     /* tp_init */
+  0,                             /* tp_alloc */
+  PyType_GenericNew              /* tp_new */
 };
index 3b10d8ca135815bcdd9c7edcc355a231e609e1b6..2d7213a8c2e54fce55f7fa5df6e5dceaaec5f179 100644 (file)
@@ -30,6 +30,7 @@
 #include "exceptions.h"
 #include "event-loop.h"
 #include "serial.h"
+#include "python.h"
 
 #include <ctype.h>
 
@@ -39,7 +40,6 @@ static int gdbpy_should_print_stack = 1;
 
 #ifdef HAVE_PYTHON
 
-#include "python.h"
 #include "libiberty.h"
 #include "cli/cli-decode.h"
 #include "charset.h"
@@ -864,6 +864,22 @@ source_python_script (FILE *stream, const char *file)
               _("Python scripting is not supported in this copy of GDB."));
 }
 
+int
+gdbpy_should_stop (struct breakpoint_object *bp_obj)
+{
+  internal_error (__FILE__, __LINE__,
+                 _("gdbpy_should_stop called when Python scripting is  " \
+                   "not supported."));
+}
+
+int
+gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj)
+{
+  internal_error (__FILE__, __LINE__,
+                 _("gdbpy_breakpoint_has_py_cond called when Python " \
+                   "scripting is not supported."));
+}
+
 #endif /* HAVE_PYTHON */
 
 \f
index 58d97fc96b8f11ff41a124503adb7ef482dd1d38..ce0eb351f739b487e4c768f77ae7cbd90a2b0a89 100644 (file)
@@ -22,6 +22,8 @@
 
 #include "value.h"
 
+struct breakpoint_object;
+
 extern int gdbpy_global_auto_load;
 
 extern void finish_python_initialization (void);
@@ -41,4 +43,8 @@ void preserve_python_values (struct objfile *objfile, htab_t copied_types);
 
 void load_auto_scripts_for_objfile (struct objfile *objfile);
 
+int gdbpy_should_stop (struct breakpoint_object *bp_obj);
+
+int gdbpy_breakpoint_has_py_cond (struct breakpoint_object *bp_obj);
+
 #endif /* GDB_PYTHON_H */
index d56f32a6ba8bd79c89c36b95b8ee452a50d4a418..a99789f8311e08b5452aa00056701634084dcea6 100644 (file)
@@ -1,3 +1,7 @@
+2010-03-14  Phil Muldoon  <pmuldoon@redhat.com>
+
+       * gdb.python/py-breakpoint.exp: Add Python stop operations tests.
+
 2011-03-13  Ulrich Weigand  <uweigand@de.ibm.com>
 
        * gdb.python/py-section-script.exp: Skip test if no Python support.
index 8f5818125a219f685740d04293f5db21feb5f868..f0a83f1c093fff72c5b2891b2e2bf5c40da59b0d 100644 (file)
@@ -195,3 +195,101 @@ 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" ".*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 stop (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 stop (self):" "" \
+  "      self.count = self.count + 1" "" \
+  "      if self.count == 9:" "" \
+  "        return True" "" \
+  "      return False" "" \
+  "end" ""
+
+gdb_py_test_multiple "Sub-class a third breakpoint" \
+  "python" "" \
+  "class basic (gdb.Breakpoint):" "" \
+  "   count = 0" "" \
+  "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."
+
+delete_breakpoints
+set cond_bp [gdb_get_line_number "Break at multiply."]
+gdb_py_test_silent_cmd  "python eval_bp1 = bp_eval(\"$cond_bp\")" "Set breakpoint" 0
+set test_cond {cond $bpnum}
+gdb_test "$test_cond \"foo==3\"" "Cannot set a condition where a Python.*" \
+    "Check you cannot add a CLI condition to a Python breakpoint that" \
+    "has defined stop"
+gdb_py_test_silent_cmd  "python eval_bp2 = basic(\"$cond_bp\")" "Set breakpoint" 0
+gdb_py_test_silent_cmd  "python eval_bp2.condition = \"1==1\"" "Set a condition" 0
+gdb_py_test_multiple "Construct an eval function" \
+  "python" "" \
+  "def stop_func ():" "" \
+  "   return True" "" \
+  "end" ""
+
+gdb_test "python eval_bp2.stop = stop_func"  \
+    "RuntimeError: Cannot set 'stop' method.*" \
+    "Assign stop function to a breakpoint that has a condition"
+
+delete_breakpoints
+gdb_breakpoint [gdb_get_line_number "Break at multiply."]
+gdb_py_test_silent_cmd  "python check_eval = bp_eval(\"$bp_location2\")" "Set breakpoint" 0
+gdb_test "python print check_eval.count" "0" \
+    "Test that evaluate function has not been yet executed (ie count = 0)"
+gdb_continue_to_breakpoint "Break at multiply." ".*/$srcfile:$bp_location2.*"
+gdb_test "python print check_eval.count" "1" \
+    "Test that evaluate function is run when location also has normal bp"
+
+gdb_py_test_multiple "Sub-class a watchpoint" \
+  "python" "" \
+  "class wp_eval (gdb.Breakpoint):" "" \
+  "   def stop (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."