[PATCH] [gdb/python] Handle bp creation and deletion in Breakpoint.stop

Tom de Vries tdevries@suse.de
Mon Dec 9 12:52:58 GMT 2024


In the documentation of gdb.Breakpoint.stop [1] we read:
...
You should not alter the execution state of the inferior (i.e., step, next,
etc.), alter the current frame context (i.e., change the current active
frame), or alter, add or delete any breakpoint.
...

PR python/32423 reports that the failure mode of attempting to delete a
breakpoint in gdb.Breakpoint.stop is:
- actually deleting the breakpoint, followed by
- a segfault.

Improve the failure mode, by instead:
- not deleting the breakpoint, and
- throwing an error.

Likewise for attempting to create a breakpoint.  Doing so reveals that
gdb.python/py-finish-breakpoint.exp contains test-cases that add a breakpoint
in Breakpoint.stop.  Drop these incorrect tests.

Tested on x86_64-linux.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32423

[1] https://sourceware.org/gdb/current/onlinedocs/gdb.html/Breakpoints-In-Python.html#index-Breakpoint_002estop
---
 gdb/extension.c                               |  7 ++
 gdb/extension.h                               |  2 +
 gdb/python/py-breakpoint.c                    | 14 +++
 gdb/python/py-finishbreakpoint.c              |  7 ++
 gdb/testsuite/gdb.python/py-breakpoint.exp    | 94 +++++++++++++++++++
 .../gdb.python/py-finish-breakpoint.exp       | 91 ------------------
 .../gdb.python/py-finish-breakpoint.py        | 25 -----
 7 files changed, 124 insertions(+), 116 deletions(-)

diff --git a/gdb/extension.c b/gdb/extension.c
index b78ea4f2716..1cc913b94dc 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -599,12 +599,19 @@ get_breakpoint_cond_ext_lang (struct breakpoint *b,
   return NULL;
 }
 
+/* Whether breakpoint_ext_lang_cond_says_stop is currently being executed.  */
+
+bool in_breakpoint_ext_lang_cond_says_stop;
+
 /* Return whether a stop condition for breakpoint B says to stop.
    True is also returned if there is no stop condition for B.  */
 
 bool
 breakpoint_ext_lang_cond_says_stop (struct breakpoint *b)
 {
+  scoped_restore restore_in_gdb_breakpoint_stop
+    = make_scoped_restore (&in_breakpoint_ext_lang_cond_says_stop, true);
+
   enum ext_lang_bp_stop stop = EXT_LANG_BP_STOP_UNSET;
 
   for (const struct extension_language_defn *extlang : extension_languages)
diff --git a/gdb/extension.h b/gdb/extension.h
index fc6020a759c..77720cd794a 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -313,6 +313,8 @@ extern void preserve_ext_lang_values (struct objfile *,
 extern const struct extension_language_defn *get_breakpoint_cond_ext_lang
   (struct breakpoint *b, enum extension_language skip_lang);
 
+extern bool in_breakpoint_ext_lang_cond_says_stop;
+
 extern bool breakpoint_ext_lang_cond_says_stop (struct breakpoint *);
 
 /* If a method with name METHOD_NAME is to be invoked on an object of type
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 75f50e1f423..af80afbc6aa 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -433,6 +433,13 @@ bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
 static PyObject *
 bppy_delete_breakpoint (PyObject *self, PyObject *args)
 {
+  if (in_breakpoint_ext_lang_cond_says_stop)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot delete a breakpoint during Breakpoint.stop"));
+      return nullptr;
+    }
+
   gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
 
   BPPY_REQUIRE_VALID (self_bp);
@@ -912,6 +919,13 @@ bppy_init_validate_args (const char *spec, char *source,
 static int
 bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 {
+  if (in_breakpoint_ext_lang_cond_says_stop)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot create a breakpoint during Breakpoint.stop"));
+      return -1;
+    }
+
   static const char *keywords[] = { "spec", "type", "wp_class", "internal",
 				    "temporary","source", "function",
 				    "label", "line", "qualified", NULL };
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index bc53d4ed13d..d8e91adc28a 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -165,6 +165,13 @@ bpfinishpy_post_stop_hook (struct gdbpy_breakpoint_object *bp_obj)
 static int
 bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 {
+  if (in_breakpoint_ext_lang_cond_says_stop)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot create a breakpoint during Breakpoint.stop"));
+      return -1;
+    }
+
   static const char *keywords[] = { "frame", "internal", NULL };
   struct finish_breakpoint_object *self_bpfinish =
       (struct finish_breakpoint_object *) self;
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index 6f9245c75c6..b6270157e7c 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -950,6 +950,98 @@ proc_with_prefix test_bkpt_auto_disable { } {
     gdb_test "continue" "False.*" "auto-disabling after enable count reached"
 }
 
+proc_with_prefix test_bkpt_stop_delete { } {
+
+    foreach_with_prefix type { gdb.Breakpoint gdb.FinishBreakpoint } {
+	# Start with a fresh gdb.
+	clean_restart $::testfile
+
+	if { ![runto_main] } {
+	    return 0
+	}
+
+	delete_breakpoints
+
+	gdb_test_multiline "Sub-class a breakpoint" \
+	    "python" "" \
+	    "class basic ($type):" "" \
+	    "    def stop(self):" "" \
+	    "        self.delete()" "" \
+	    "        return True" "" \
+	    "end" ""
+
+	set line_marker "Break at function add.";
+	set bp_location [gdb_get_line_number $line_marker]
+
+	if { $type == "gdb.Breakpoint" } {
+	    gdb_py_test_silent_cmd "python basic(\"$bp_location\")" \
+		"Set breakpoint" 0
+	} else {
+	    gdb_breakpoint $bp_location
+	    gdb_continue_to_breakpoint "continue to function add" \
+		.*[string_to_regexp $line_marker].*
+
+	    gdb_py_test_silent_cmd "python basic()" \
+		"Set breakpoint" 0
+	}
+
+	set err_msg_prefix "Python Exception <class 'RuntimeError'>"
+	set err_msg "Cannot delete a breakpoint during Breakpoint.stop"
+	gdb_test "continue" \
+	    [string_to_regexp "$err_msg_prefix: $err_msg"].* \
+	    $err_msg
+    }
+}
+
+proc_with_prefix test_bkpt_stop_create { } {
+
+    foreach_with_prefix type { gdb.Breakpoint gdb.FinishBreakpoint } {
+	# Start with a fresh gdb.
+	clean_restart $::testfile
+
+	if { ![runto_main] } {
+	    return 0
+	}
+
+	delete_breakpoints
+
+	set line_marker "Break at function add.";
+	set bp_location [gdb_get_line_number $line_marker]
+
+	if { $type == "gdb.Breakpoint" } {
+	    set action "basic(\"$bp_location\")"
+	} else {
+	    set action "basic()"
+	}
+
+	gdb_test_multiline "Sub-class a breakpoint" \
+	    "python" "" \
+	    "class basic ($type):" "" \
+	    "    def stop(self):" "" \
+	    "        $action" "" \
+	    "        return True" "" \
+	    "end" ""
+
+	if { $type == "gdb.Breakpoint" } {
+	    gdb_py_test_silent_cmd "python basic(\"$bp_location\")" \
+		"Set breakpoint" 0
+	} else {
+	    gdb_breakpoint $bp_location
+	    gdb_continue_to_breakpoint "continue to function add" \
+		.*[string_to_regexp $line_marker].*
+
+	    gdb_py_test_silent_cmd "python basic()" \
+		"Set breakpoint" 0
+	}
+
+	set err_msg_prefix "Python Exception <class 'RuntimeError'>"
+	set err_msg "Cannot create a breakpoint during Breakpoint.stop"
+	gdb_test "continue" \
+	    [string_to_regexp "$err_msg_prefix: $err_msg"].* \
+	    $err_msg
+    }
+}
+
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
@@ -968,3 +1060,5 @@ test_bkpt_explicit_loc
 test_bkpt_qualified
 test_bkpt_probe
 test_bkpt_auto_disable
+test_bkpt_stop_delete
+test_bkpt_stop_create
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint.exp b/gdb/testsuite/gdb.python/py-finish-breakpoint.exp
index 0316bc75985..114ba96a3c5 100644
--- a/gdb/testsuite/gdb.python/py-finish-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.exp
@@ -165,97 +165,6 @@ with_test_prefix "function returned by longjump" {
 	"ensure that finish bp is invalid after out of scope notification"
 }
 
-#
-# Test FinishBreakpoint in BP condition evaluation 
-# (finish in dummy frame)
-#
-
-with_test_prefix "finish in dummy frame" {
-    clean_restart ${testfile}
-    gdb_load_shlib ${lib_sl}
-
-    gdb_test "source $python_file" "Python script imported.*" \
-	"import python scripts"
-
-
-    if {![runto_main]} {
-	return 0
-    }
-
-    gdb_test "break ${cond_line} if test_1(i,8)" "Breakpoint .* at .*" \
-	"set a conditional BP"
-    gdb_test "python TestBreakpoint()" "TestBreakpoint init" \
-	"set FinishBP in a breakpoint condition"
-    gdb_test "continue" \
-	"\"FinishBreakpoint\" cannot be set on a dummy frame.*" \
-	"don't allow FinishBreakpoint on dummy frames"
-    gdb_test "print i" "8" \
-	"check stopped location"
-}
-
-#
-# Test FinishBreakpoint in BP condition evaluation 
-# (finish in normal frame)
-#
-
-with_test_prefix "finish in normal frame" {
-    clean_restart ${testfile}
-    gdb_load_shlib ${lib_sl}
-
-    gdb_test "source $python_file" "Python script imported.*" \
-	"import python scripts"
-
-    if {![runto_main]} {
-	return 0
-    }
-
-    gdb_test "break ${cond_line} if test(i,8)" \
-	"Breakpoint .* at .*" "set conditional BP"
-    gdb_test "python TestBreakpoint()" "TestBreakpoint init" "set BP in condition"
-
-    gdb_test "continue" \
-	"test don't stop: 1.*test don't stop: 2.*test stop.*Error in testing condition for breakpoint ${::decimal}.*The program being debugged stopped while in a function called from GDB.*" \
-	"stop in condition function"
-
-    gdb_test "continue" "Continuing.*" "finish condition evaluation"
-    gdb_test "continue" "Breakpoint.*" "stop at conditional breakpoint"
-    gdb_test "print i" "8" \
-	"check stopped location"
-}
-
-#
-# Test FinishBreakpoint in explicit inferior function call
-#
-
-with_test_prefix "explicit inferior function call" {
-    clean_restart ${testfile}
-    gdb_load_shlib ${lib_sl}
-
-    gdb_test "source $python_file" "Python script imported.*" \
-	"import python scripts"
-
-    if {![runto_main]} {
-	return 0
-    }
-
-    # return address in dummy frame
-
-    gdb_test "python TestExplicitBreakpoint('increase_1')" "Breakpoint.*at.*" \
-	"prepare TestExplicitBreakpoint, return addr in dummy frame"
-    gdb_test "print increase_1(&i)" \
-	"\"FinishBreakpoint\" cannot be set on a dummy frame.*" \
-	"don't allow FinishBreakpoint on dummy frames, return address in dummy frame"
-
-    # return address in normal frame
-
-    delete_breakpoints
-    gdb_test "python TestExplicitBreakpoint(\"increase_1\")" "Breakpoint.*at.*" \
-	"prepare TestExplicitBreakpoint, return addr in normal frame"
-    gdb_test "print increase(&i)" \
-	"SimpleFinishBreakpoint init.*SimpleFinishBreakpoint stop.*The program being debugged stopped while in a function called from GDB.*" \
-	"FinishBP stop"
-}
-
 #
 # Test FinishBreakpoint when inferior exits
 #
diff --git a/gdb/testsuite/gdb.python/py-finish-breakpoint.py b/gdb/testsuite/gdb.python/py-finish-breakpoint.py
index dba0431636d..374166af8ae 100644
--- a/gdb/testsuite/gdb.python/py-finish-breakpoint.py
+++ b/gdb/testsuite/gdb.python/py-finish-breakpoint.py
@@ -33,22 +33,6 @@ class MyFinishBreakpoint(gdb.FinishBreakpoint):
         print("MyFinishBreakpoint out of scope")
 
 
-class TestBreakpoint(gdb.Breakpoint):
-    def __init__(self):
-        gdb.Breakpoint.__init__(self, spec="test_1", internal=1)
-        self.silent = True
-        self.count = 0
-        print("TestBreakpoint init")
-
-    def stop(self):
-        self.count += 1
-        try:
-            TestFinishBreakpoint(gdb.newest_frame(), self.count)
-        except ValueError as e:
-            print(e)
-        return False
-
-
 class TestFinishBreakpoint(gdb.FinishBreakpoint):
     def __init__(self, frame, count):
         self.count = count
@@ -67,15 +51,6 @@ class TestFinishBreakpoint(gdb.FinishBreakpoint):
         print("test didn't finish: %d" % self.count)
 
 
-class TestExplicitBreakpoint(gdb.Breakpoint):
-    def stop(self):
-        try:
-            SimpleFinishBreakpoint(gdb.newest_frame())
-        except ValueError as e:
-            print(e)
-        return False
-
-
 class SimpleFinishBreakpoint(gdb.FinishBreakpoint):
     def __init__(self, frame):
         gdb.FinishBreakpoint.__init__(self, frame)

base-commit: d9df3857da0cef29ed9c0ef75f90700c5f392986
-- 
2.43.0



More information about the Gdb-patches mailing list