- Patches added: * gdb-catchpoint-re-set.patch OBS-URL: https://build.opensuse.org/package/show/devel:gcc/gdb?expand=0&rev=430
576 lines
19 KiB
Diff
576 lines
19 KiB
Diff
From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
|
||
From: Andrew Burgess <aburgess@redhat.com>
|
||
Date: Wed, 14 Aug 2024 15:16:46 +0100
|
||
Subject: gdb-catchpoint-re-set.patch
|
||
|
||
;; Backport upstream commit a92e943014f to fix rhbz2304296.
|
||
|
||
gdb: implement ::re_set method for catchpoint class
|
||
|
||
It is possible to attach a condition to a catchpoint. This can't be
|
||
done when the catchpoint is created, but can be done with the
|
||
'condition' command, this is documented in the GDB manual:
|
||
|
||
You can also use the 'if' keyword with the 'watch' command. The
|
||
'catch' command does not recognize the 'if' keyword; 'condition' is the
|
||
only way to impose a further condition on a catchpoint.
|
||
|
||
A GDB crash was reported against Fedora GDB where a user had attached
|
||
a condition to a catchpoint and then restarted the inferior. When the
|
||
catchpoint was hit GDB would immediately segfault. I was able to
|
||
reproduce the failure on upstream GDB:
|
||
|
||
(gdb) file ./some/binary
|
||
(gdb) catch syscall write
|
||
(gdb) run
|
||
...
|
||
Catchpoint 1 (returned from syscall write), 0x00007ffff7b594a7 in write () from /lib64/libc.so.6
|
||
(gdb) condition 1 $_streq((char *) $rsi, "foobar") == 0
|
||
(gdb) run
|
||
...
|
||
Fatal signal: Segmentation fault
|
||
...
|
||
|
||
What happened here is that on the system in question we had debug
|
||
information available for both the main application and also for
|
||
libc.
|
||
|
||
When the condition was attached GDB was stopped inside libc and as the
|
||
debug information was available GDB found a reference to the 'char'
|
||
type (for the cast) inside libc's debug information.
|
||
|
||
When the inferior is restarted GDB discards all of the objfiles
|
||
associated with shared libraries, and this includes libc. As such the
|
||
'char' type, which is objfile owned, is discarded and the reference to
|
||
it from the catchpoint's condition expression becomes invalid.
|
||
|
||
Now, if it were a breakpoint instead of a catchpoint, what would
|
||
happen is that after the shared library objfiles had been discarded
|
||
we'd call the virtual breakpoint::re_set method on the breakpoint, and
|
||
this would update the breakpoint's condition expression. This is
|
||
because user breakpoints are actually instances of the code_breakpoint
|
||
class and the code_breakpoint::re_set method contains the code to
|
||
recompute the breakpoint's condition expression.
|
||
|
||
However, catchpoints are instances of the catchpoint class which
|
||
inherits from the base breakpoint class. The catchpoint class does
|
||
not override breakpoint::re_set, and breakpoint::re_set is empty!
|
||
|
||
The consequence of this is that catchpoint condition expressions are
|
||
never recomputed, and the dangling pointer to the now deleted, objfile
|
||
owned type 'char' is left around, and, when the catchpoint is hit, the
|
||
invalid pointer is used when GDB tries to evaluate the condition
|
||
expression.
|
||
|
||
In this commit I have implemented catchpoint::re_set. This is pretty
|
||
simple and just recomputes the condition expression as you'd expect.
|
||
If the condition doesn't evaluate then the catchpoint is marked as
|
||
disabled_by_cond.
|
||
|
||
I have also made breakpoint::re_set pure virtual. With the addition
|
||
of catchpoint::re_set every sub-class of breakpoint now implements the
|
||
::re_set method, and if new sub-classes are added in the future I
|
||
think that they _must_ implement ::re_set in order to avoid this
|
||
problem. As such falling back to an empty breakpoint::re_set doesn't
|
||
seem helpful.
|
||
|
||
For testing I have not relied on stopping in libc and having libc
|
||
debug information available, this doesn't seem like a good idea for
|
||
the GDB testsuite. Instead I create a (rather pointless) condition
|
||
check that uses a type defined only within a shared library. When the
|
||
inferior is restarted the catchpoint will temporarily be marked as
|
||
disabled_by_cond (due to the type not being available), but once the
|
||
shared library is loaded again the catchpoint will be re-enabled.
|
||
Without the fixes above then the same crashing behaviour can be
|
||
observed.
|
||
|
||
One point of note: the dangling pointer of course exposes undefined
|
||
behaviour, with no guarantee of a crash. Though a crash is what I
|
||
usually see I have see GDB throw random errors from the expression
|
||
evaluation code, and once, I saw no problem at all! If you recompile
|
||
GDB with the address sanitizer, or run under valgrind, then the bug
|
||
will be exposed every time.
|
||
|
||
After fixing this bug I checked bugzilla and found PR gdb/29960 which
|
||
is the same bug. I was able to reproduce the bug before this commit,
|
||
and after this commit GDB is no longer crashing.
|
||
|
||
Before:
|
||
|
||
(gdb) file /tmp/hello.x
|
||
Reading symbols from /tmp/hello.x...
|
||
(gdb) run
|
||
Starting program: /tmp/hello.x
|
||
Hello World
|
||
[Inferior 1 (process 1101855) exited normally]
|
||
(gdb) catch syscall 1
|
||
Catchpoint 1 (syscall 'write' [1])
|
||
(gdb) condition 1 write.fd == 1
|
||
(gdb) run
|
||
Starting program: /tmp/hello.x
|
||
|
||
Fatal signal: Segmentation fault
|
||
...
|
||
|
||
And after:
|
||
|
||
(gdb) file /tmp/hello.x
|
||
Reading symbols from /tmp/hello.x...
|
||
(gdb) run
|
||
Starting program: /tmp/hello.x
|
||
Hello World
|
||
Args: ( 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 )
|
||
[Inferior 1 (process 1102373) exited normally]
|
||
(gdb) catch syscall 1
|
||
Catchpoint 1 (syscall 'write' [1])
|
||
(gdb) condition 1 write.fd == 1
|
||
(gdb) r
|
||
Starting program: /tmp/hello.x
|
||
Error in testing condition for breakpoint 1:
|
||
Attempt to extract a component of a value that is not a structure.
|
||
|
||
Catchpoint 1 (call to syscall write), 0x00007ffff7eb94a7 in write ()
|
||
from /lib64/libc.so.6
|
||
(gdb) ptype write
|
||
type = <unknown return type> ()
|
||
(gdb)
|
||
|
||
Notice we get the error now when the condition fails to evaluate.
|
||
This seems reasonable given that 'write' will be a function, and
|
||
indeed the final 'ptype' shows that it's a function, not a struct.
|
||
|
||
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29960
|
||
|
||
Reviewed-By: Tom de Vries <tdevries@suse.de>
|
||
|
||
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
|
||
--- a/gdb/breakpoint.c
|
||
+++ b/gdb/breakpoint.c
|
||
@@ -8146,6 +8146,60 @@ catchpoint::catchpoint (struct gdbarch *gdbarch, bool temp,
|
||
pspace = current_program_space;
|
||
}
|
||
|
||
+/* See breakpoint.h. */
|
||
+
|
||
+void
|
||
+catchpoint::re_set ()
|
||
+{
|
||
+ /* All catchpoints are associated with a specific program_space. */
|
||
+ gdb_assert (pspace != nullptr);
|
||
+
|
||
+ /* Catchpoints have a single dummy location. */
|
||
+ gdb_assert (locations ().size () == 1);
|
||
+ bp_location &bl = m_locations.front ();
|
||
+
|
||
+ if (cond_string == nullptr)
|
||
+ {
|
||
+ /* It shouldn't be possible to have a parsed condition expression
|
||
+ cached on this location if the catchpoint doesn't have a condition
|
||
+ string set. */
|
||
+ gdb_assert (bl.cond == nullptr);
|
||
+
|
||
+ /* Nothing to re-compute, and the catchpoint cannot change. */
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ bool previous_disabled_by_cond = bl.disabled_by_cond;
|
||
+
|
||
+ /* Start by marking the location disabled and discarding the previously
|
||
+ computed condition expression. Now if we get an exception, even if
|
||
+ it's a quit exception, we'll leave the location disabled and there
|
||
+ will be no (possibly invalid) expression cached. */
|
||
+ bl.disabled_by_cond = true;
|
||
+ bl.cond = nullptr;
|
||
+
|
||
+ const char *s = cond_string.get ();
|
||
+ try
|
||
+ {
|
||
+ switch_to_program_space_and_thread (pspace);
|
||
+
|
||
+ bl.cond = parse_exp_1 (&s, bl.address, block_for_pc (bl.address),
|
||
+ nullptr);
|
||
+ bl.disabled_by_cond = false;
|
||
+ }
|
||
+ catch (const gdb_exception_error &e)
|
||
+ {
|
||
+ /* Any exception thrown must be from either the parse_exp_1 or
|
||
+ earlier in the try block. As such the following two asserts
|
||
+ should be true. */
|
||
+ gdb_assert (bl.disabled_by_cond);
|
||
+ gdb_assert (bl.cond == nullptr);
|
||
+ }
|
||
+
|
||
+ if (previous_disabled_by_cond != bl.disabled_by_cond)
|
||
+ notify_breakpoint_modified (this);
|
||
+}
|
||
+
|
||
/* Notify interpreters and observers that breakpoint B was created. */
|
||
|
||
static void
|
||
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
|
||
--- a/gdb/breakpoint.h
|
||
+++ b/gdb/breakpoint.h
|
||
@@ -702,11 +702,10 @@ struct breakpoint : public intrusive_list_node<breakpoint>
|
||
|
||
/* Reevaluate a breakpoint. This is necessary after symbols change
|
||
(e.g., an executable or DSO was loaded, or the inferior just
|
||
- started). */
|
||
- virtual void re_set ()
|
||
- {
|
||
- /* Nothing to re-set. */
|
||
- }
|
||
+ started). This is pure virtual as, at a minimum, each sub-class must
|
||
+ recompute any cached condition expressions based off of the
|
||
+ cond_string member variable. */
|
||
+ virtual void re_set () = 0;
|
||
|
||
/* Insert the breakpoint or watchpoint or activate the catchpoint.
|
||
Return 0 for success, 1 if the breakpoint, watchpoint or
|
||
@@ -1120,6 +1119,10 @@ struct catchpoint : public breakpoint
|
||
catchpoint (struct gdbarch *gdbarch, bool temp, const char *cond_string);
|
||
|
||
~catchpoint () override = 0;
|
||
+
|
||
+ /* If the catchpoint has a condition set then recompute the cached
|
||
+ expression within the single dummy location. */
|
||
+ void re_set () override;
|
||
};
|
||
|
||
|
||
diff --git a/gdb/testsuite/gdb.base/reset-catchpoint-cond-lib.c b/gdb/testsuite/gdb.base/reset-catchpoint-cond-lib.c
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.base/reset-catchpoint-cond-lib.c
|
||
@@ -0,0 +1,76 @@
|
||
+/* This testcase is part of GDB, the GNU debugger.
|
||
+
|
||
+ Copyright 2024 Free Software Foundation, Inc.
|
||
+
|
||
+ This program is free software; you can redistribute it and/or modify
|
||
+ it under the terms of the GNU General Public License as published by
|
||
+ the Free Software Foundation; either version 3 of the License, or
|
||
+ (at your option) any later version.
|
||
+
|
||
+ This program is distributed in the hope that it will be useful,
|
||
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
+ GNU General Public License for more details.
|
||
+
|
||
+ You should have received a copy of the GNU General Public License
|
||
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||
+
|
||
+#include <stdio.h>
|
||
+#include <signal.h>
|
||
+#include <unistd.h>
|
||
+#include <sys/types.h>
|
||
+#include <sys/wait.h>
|
||
+#include <unistd.h>
|
||
+#include <assert.h>
|
||
+#include <stdlib.h>
|
||
+
|
||
+/* This type is used by GDB. */
|
||
+struct lib_type
|
||
+{
|
||
+ int a;
|
||
+ int b;
|
||
+ int c;
|
||
+};
|
||
+
|
||
+/* Ensure the type above is used. */
|
||
+volatile struct lib_type global_lib_object = { 1, 2, 3 };
|
||
+
|
||
+/* This pointer is checked by GDB. */
|
||
+volatile void *opaque_ptr = 0;
|
||
+
|
||
+void
|
||
+lib_func_test_syscall (void)
|
||
+{
|
||
+ puts ("Inside library\n");
|
||
+ fflush (stdout);
|
||
+}
|
||
+
|
||
+static void
|
||
+sig_handler (int signo)
|
||
+{
|
||
+ /* Nothing. */
|
||
+}
|
||
+
|
||
+void
|
||
+lib_func_test_signal (void)
|
||
+{
|
||
+ signal (SIGUSR1, sig_handler);
|
||
+
|
||
+ kill (getpid (), SIGUSR1);
|
||
+}
|
||
+
|
||
+void
|
||
+lib_func_test_fork (void)
|
||
+{
|
||
+ pid_t pid = fork ();
|
||
+ assert (pid != -1);
|
||
+
|
||
+ if (pid == 0)
|
||
+ {
|
||
+ /* Child: just exit. */
|
||
+ exit (0);
|
||
+ }
|
||
+
|
||
+ /* Parent. */
|
||
+ waitpid (pid, NULL, 0);
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.base/reset-catchpoint-cond.c b/gdb/testsuite/gdb.base/reset-catchpoint-cond.c
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.base/reset-catchpoint-cond.c
|
||
@@ -0,0 +1,50 @@
|
||
+/* This testcase is part of GDB, the GNU debugger.
|
||
+
|
||
+ Copyright 2024 Free Software Foundation, Inc.
|
||
+
|
||
+ This program is free software; you can redistribute it and/or modify
|
||
+ it under the terms of the GNU General Public License as published by
|
||
+ the Free Software Foundation; either version 3 of the License, or
|
||
+ (at your option) any later version.
|
||
+
|
||
+ This program is distributed in the hope that it will be useful,
|
||
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
+ GNU General Public License for more details.
|
||
+
|
||
+ You should have received a copy of the GNU General Public License
|
||
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||
+
|
||
+extern void lib_func_test_syscall (void);
|
||
+extern void lib_func_test_signal (void);
|
||
+extern void lib_func_test_fork (void);
|
||
+
|
||
+/* We use this to perform some filler work. */
|
||
+volatile int global_var = 0;
|
||
+
|
||
+/* Just somewhere for GDB to put a breakpoint. */
|
||
+void
|
||
+breakpt_before_exit (void)
|
||
+{
|
||
+ /* Nothing. */
|
||
+}
|
||
+
|
||
+int
|
||
+main (void)
|
||
+{
|
||
+#if defined TEST_SYSCALL
|
||
+ lib_func_test_syscall ();
|
||
+#elif defined TEST_SIGNAL
|
||
+ lib_func_test_signal ();
|
||
+#elif defined TEST_FORK
|
||
+ lib_func_test_fork ();
|
||
+#else
|
||
+# error compile with suitable -DTEST_xxx macro defined
|
||
+#endif
|
||
+
|
||
+ ++global_var;
|
||
+
|
||
+ breakpt_before_exit ();
|
||
+
|
||
+ return 0;
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.base/reset-catchpoint-cond.exp b/gdb/testsuite/gdb.base/reset-catchpoint-cond.exp
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.base/reset-catchpoint-cond.exp
|
||
@@ -0,0 +1,169 @@
|
||
+# Copyright 2024 Free Software Foundation, Inc.
|
||
+
|
||
+# This program is free software; you can redistribute it and/or modify
|
||
+# it under the terms of the GNU General Public License as published by
|
||
+# the Free Software Foundation; either version 3 of the License, or
|
||
+# (at your option) any later version.
|
||
+#
|
||
+# This program is distributed in the hope that it will be useful,
|
||
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
+# GNU General Public License for more details.
|
||
+#
|
||
+# You should have received a copy of the GNU General Public License
|
||
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+
|
||
+# Test that the condition for a catchpoint is correctly reset after
|
||
+# shared libraries are unloaded, as happens when an inferior is
|
||
+# restarted.
|
||
+#
|
||
+# If this is not done then, when the catchpoint is hit on the second
|
||
+# run, we'll evaluate the parsed expression from the first run, which
|
||
+# might include references to types owned by the now deleted objfile
|
||
+# (for the shared library loaded in the first run).
|
||
+#
|
||
+# This scripts tests a number of different catchpoint types. Inside
|
||
+# GDB these are all sub-classes of the 'catchpoint' type, which is
|
||
+# where the fix for the above issue resides, so all catchpoint types
|
||
+# should work correctly.
|
||
+
|
||
+standard_testfile .c -lib.c
|
||
+
|
||
+set libfile $binfile-lib.so
|
||
+
|
||
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
|
||
+
|
||
+if {[build_executable "build shared library" $libfile $srcfile2 \
|
||
+ {debug shlib}] == -1} {
|
||
+ return
|
||
+}
|
||
+
|
||
+# Depending on whether or not libc debug info is installed, when we
|
||
+# hit a syscall catchpoint inside libc there might be a source line
|
||
+# included in the output.
|
||
+#
|
||
+# This regexp will match an optional line and can be added to the
|
||
+# expected catchpoint output to ignore the (possibly missing) source
|
||
+# line.
|
||
+set libc_src_line_re "(?:\r\n\[^\r\n\]+)?"
|
||
+
|
||
+# Check the Python bp_modified_list and then reset the list back to
|
||
+# empty. TESTNAME is just a string. BP_NUM is a list of breakpoint
|
||
+# numbers that are expected to appear (in the given order) in the
|
||
+# bp_modified_list.
|
||
+
|
||
+proc check_modified_bp_list { testname bp_num } {
|
||
+ if { [allow_python_tests] } {
|
||
+ set expected [join $bp_num ", "]
|
||
+
|
||
+ gdb_test "python print(bp_modified_list)" "\\\[$expected\\\]" \
|
||
+ $testname
|
||
+ gdb_test_no_output -nopass "python bp_modified_list=\[\]" \
|
||
+ "reset bp_modified_list after $testname"
|
||
+ }
|
||
+}
|
||
+
|
||
+# Build an executable and run tests on 'catch MODE'.
|
||
+
|
||
+proc run_test { mode } {
|
||
+ set exec_name ${::binfile}-${mode}
|
||
+
|
||
+ set macro TEST_[string toupper $mode]
|
||
+
|
||
+ if {[build_executable "build test executable" $exec_name $::srcfile \
|
||
+ [list debug shlib=$::libfile additional_flags=-D${macro}]] == -1} {
|
||
+ return
|
||
+ }
|
||
+
|
||
+ clean_restart $exec_name
|
||
+ gdb_load_shlib $::libfile
|
||
+
|
||
+ if {![runto_main]} {
|
||
+ return
|
||
+ }
|
||
+
|
||
+ if { $mode eq "syscall" } {
|
||
+ gdb_test "catch syscall write" \
|
||
+ "Catchpoint $::decimal \\(syscall 'write' \[^)\]+\\)"
|
||
+ set catch_re "call to syscall write"
|
||
+ } elseif { $mode eq "signal" } {
|
||
+ gdb_test "catch signal SIGUSR1" \
|
||
+ "Catchpoint $::decimal \\(signal SIGUSR1\\)"
|
||
+ set catch_re "signal SIGUSR1"
|
||
+ } elseif { $mode eq "fork" } {
|
||
+ gdb_test "catch fork" \
|
||
+ "Catchpoint $::decimal \\(fork\\)"
|
||
+ set catch_re "forked process $::decimal"
|
||
+ } else {
|
||
+ error "unknown mode $mode"
|
||
+ }
|
||
+ set cp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*"]
|
||
+
|
||
+ gdb_breakpoint "breakpt_before_exit"
|
||
+
|
||
+ gdb_test "continue" \
|
||
+ "Catchpoint ${cp_num} \[^\r\n\]+$::libc_src_line_re"
|
||
+
|
||
+ if { [allow_python_tests] } {
|
||
+ gdb_test_no_output "source $::pyfile" "import python scripts"
|
||
+ check_modified_bp_list \
|
||
+ "check b/p modified observer has not yet triggered" {}
|
||
+ }
|
||
+
|
||
+ with_test_prefix "with false condition" {
|
||
+ gdb_test_no_output "condition $cp_num ((struct lib_type *) opaque_ptr) != 0" \
|
||
+ "set catchpoint condition"
|
||
+
|
||
+ check_modified_bp_list \
|
||
+ "catchpoint modified once by setting condition" \
|
||
+ [list $cp_num]
|
||
+
|
||
+ gdb_run_cmd
|
||
+ gdb_test "" [multi_line \
|
||
+ "Breakpoint $::decimal, main \\(\\) \[^\r\n\]+" \
|
||
+ "$::decimal\\s+\[^\r\n\]+"]
|
||
+
|
||
+ check_modified_bp_list "catchpoint modified twice at startup" \
|
||
+ [list $cp_num $cp_num "$::decimal"]
|
||
+
|
||
+ gdb_test "continue" \
|
||
+ [multi_line \
|
||
+ "Breakpoint $::decimal, breakpt_before_exit \\(\\) at \[^\r\n\]+" \
|
||
+ "$::decimal\\s+\[^\r\n\]+"] \
|
||
+ "continue to breakpt_before_exit"
|
||
+ }
|
||
+
|
||
+ # Check the bp_modified_list against '.*'. We don't care at this
|
||
+ # point what's in the list (nothing relevant has happened since we
|
||
+ # last checked), but this has the side effect of clearing the list.
|
||
+ check_modified_bp_list "clear bp modified list" { .* }
|
||
+
|
||
+ with_test_prefix "with true condition" {
|
||
+ gdb_test_no_output "condition $cp_num ((struct lib_type *) opaque_ptr) == 0" \
|
||
+ "set catchpoint condition"
|
||
+
|
||
+ check_modified_bp_list \
|
||
+ "catchpoint modified once by setting condition" \
|
||
+ [list $cp_num]
|
||
+
|
||
+ gdb_run_cmd
|
||
+ gdb_test "" [multi_line \
|
||
+ "Breakpoint $::decimal, main \\(\\) \[^\r\n\]+" \
|
||
+ "$::decimal\\s+\[^\r\n\]+"]
|
||
+
|
||
+ check_modified_bp_list "catchpoint modified twice at startup" \
|
||
+ [list $cp_num $cp_num "$::decimal"]
|
||
+
|
||
+ gdb_test "continue" \
|
||
+ "Catchpoint $cp_num \\($catch_re\\), \[^\r\n\]+$::libc_src_line_re" \
|
||
+ "continue until catchpoint hit"
|
||
+
|
||
+ check_modified_bp_list "catchpoint modified again when hit" \
|
||
+ [list $cp_num]
|
||
+ }
|
||
+}
|
||
+
|
||
+# Run the tests.
|
||
+foreach_with_prefix mode { syscall signal fork } {
|
||
+ run_test $mode
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.base/reset-catchpoint-cond.py b/gdb/testsuite/gdb.base/reset-catchpoint-cond.py
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.base/reset-catchpoint-cond.py
|
||
@@ -0,0 +1,21 @@
|
||
+# Copyright (C) 2024 Free Software Foundation, Inc.
|
||
+
|
||
+# This program is free software; you can redistribute it and/or modify
|
||
+# it under the terms of the GNU General Public License as published by
|
||
+# the Free Software Foundation; either version 3 of the License, or
|
||
+# (at your option) any later version.
|
||
+#
|
||
+# This program is distributed in the hope that it will be useful,
|
||
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
+# GNU General Public License for more details.
|
||
+#
|
||
+# You should have received a copy of the GNU General Public License
|
||
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+
|
||
+bp_modified_list = []
|
||
+
|
||
+def bp_modified(bp):
|
||
+ bp_modified_list.append (bp.number)
|
||
+
|
||
+gdb.events.breakpoint_modified.connect(bp_modified)
|