This commit rewrites disable_breakpoints_in_unloaded_shlib to be more
like disable_breakpoints_in_freed_objfile. Instead of looping over
all b/p locations, we instead loop over all b/p and then over all
locations for each b/p.
The advantage of doing this is that we can fix the small bug that was
documented in a comment in the code:
/* This may cause duplicate notifications for the same breakpoint. */
notify_breakpoint_modified (b);
By calling notify_breakpoint_modified() as we modify each location we
can potentially send multiple notifications for a single b/p.
Is this a bug? Maybe not. After all, at each notification one of the
locations will have changed, so its probably fine. But it's not
ideal, and we can easily do better, so lets do that.
There's a new test which checks that we only get a single notification
when the shared library is unloaded. Note that the test is written as
if there are multiple related but different tests within the same test
file ... but there aren't currently! The next commit will add another
test proc to this test script at which point the comments will make
sense. I've done this to avoid unnecessary churn in the next commit.
Tested-By: Hannes Domani <ssbssa@yahoo.de>
---
gdb/breakpoint.c | 45 +++++----
gdb/testsuite/gdb.base/shlib-unload-lib.c | 30 ++++++
gdb/testsuite/gdb.base/shlib-unload.c | 63 ++++++++++++
gdb/testsuite/gdb.base/shlib-unload.exp | 114 ++++++++++++++++++++++
gdb/testsuite/gdb.base/shlib-unload.h | 26 +++++
gdb/testsuite/gdb.base/shlib-unload.py | 31 ++++++
6 files changed, 292 insertions(+), 17 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/shlib-unload-lib.c
create mode 100644 gdb/testsuite/gdb.base/shlib-unload.c
create mode 100644 gdb/testsuite/gdb.base/shlib-unload.exp
create mode 100644 gdb/testsuite/gdb.base/shlib-unload.h
create mode 100644 gdb/testsuite/gdb.base/shlib-unload.py
@@ -8079,29 +8079,37 @@ disable_breakpoints_in_unloaded_shlib (program_space *pspace, const solib &solib
{
bool disabled_shlib_breaks = false;
- for (bp_location *loc : all_bp_locations ())
+ for (breakpoint &b : all_breakpoints ())
{
- /* ALL_BP_LOCATIONS bp_location has LOC->OWNER always non-NULL. */
- struct breakpoint *b = loc->owner;
+ bool bp_modified = false;
- if (pspace == loc->pspace
- && !loc->shlib_disabled
- && (((b->type == bp_breakpoint
- || b->type == bp_jit_event
- || b->type == bp_hardware_breakpoint)
- && (loc->loc_type == bp_loc_hardware_breakpoint
- || loc->loc_type == bp_loc_software_breakpoint))
- || is_tracepoint (b))
- && solib_contains_address_p (solib, loc->address))
+ if (b.type != bp_breakpoint
+ && b.type != bp_jit_event
+ && b.type != bp_hardware_breakpoint
+ && !is_tracepoint (&b))
+ continue;
+
+ for (bp_location &loc : b.locations ())
{
- loc->shlib_disabled = 1;
+ if (pspace != loc.pspace || loc.shlib_disabled)
+ continue;
+
+ if (loc.loc_type != bp_loc_hardware_breakpoint
+ && loc.loc_type != bp_loc_software_breakpoint
+ && !is_tracepoint (&b))
+ continue;
+
+ if (!solib_contains_address_p (solib, loc.address))
+ continue;
+
+ loc.shlib_disabled = 1;
+
/* At this point, we cannot rely on remove_breakpoint
succeeding so we must mark the breakpoint as not inserted
to prevent future errors occurring in remove_breakpoints. */
- loc->inserted = 0;
+ loc.inserted = 0;
- /* This may cause duplicate notifications for the same breakpoint. */
- notify_breakpoint_modified (b);
+ bp_modified = true;
if (!disabled_shlib_breaks)
{
@@ -8109,9 +8117,12 @@ disable_breakpoints_in_unloaded_shlib (program_space *pspace, const solib &solib
warning (_("Temporarily disabling breakpoints "
"for unloaded shared library \"%s\""),
solib.so_name.c_str ());
+ disabled_shlib_breaks = true;
}
- disabled_shlib_breaks = true;
}
+
+ if (bp_modified)
+ notify_breakpoint_modified (&b);
}
}
new file mode 100644
@@ -0,0 +1,30 @@
+/* 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 "shlib-unload.h"
+
+int
+foo (void)
+{
+ return inline_func ();
+}
+
+int
+bar (void)
+{
+ return inline_func ();
+}
new file mode 100644
@@ -0,0 +1,63 @@
+/* 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 <stdlib.h>
+
+#ifdef __WIN32__
+#include <windows.h>
+#define dlopen(name, mode) LoadLibrary (TEXT (name))
+#ifdef _WIN32_WCE
+# define dlsym(handle, func) GetProcAddress (handle, TEXT (func))
+#else
+# define dlsym(handle, func) GetProcAddress (handle, func)
+#endif
+#define dlclose(handle) FreeLibrary (handle)
+#else
+#include <dlfcn.h>
+#endif
+
+#include <assert.h>
+
+#include "shlib-unload.h"
+
+int
+main (void)
+{
+ int res;
+ void *handle;
+ int (*func) (void);
+
+ int val = inline_func ();
+
+ handle = dlopen (SHLIB_NAME, RTLD_LAZY);
+ assert (handle != NULL);
+
+ func = (int (*)(void)) dlsym (handle, "foo");
+ assert (func != NULL);
+
+ val += func ();
+
+ func = (int (*)(void)) dlsym (handle, "bar");
+ assert (func != NULL);
+
+ val += func ();
+
+ res = dlclose (handle); /* Break here. */
+ assert (res == 0);
+
+ return val;
+}
new file mode 100644
@@ -0,0 +1,114 @@
+# 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/>.
+
+# Tests for GDB's handling of a shared library being unloaded via a
+# call to dlclose. See the individual test_* procs for a description
+# of each test.
+
+standard_testfile .c -lib.c
+
+# One of the tests uses this Python file. The test_* proc checks that
+# GDB supports Python tests. Some of the other procs don't use this
+# Python file.
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+# Build the library and copy it to the target.
+set libname ${testfile}-lib
+set libfile [standard_output_file $libname]
+if { [build_executable "build shlib" $libfile $srcfile2 {debug shlib}] == -1} {
+ return
+}
+set libfile_on_target [gdb_download_shlib $libfile]
+
+# Build the executable.
+set opts [list debug shlib_load additional_flags=-DSHLIB_NAME=\"${libname}\"]
+if { [build_executable "build exec" $binfile $srcfile $opts] == -1} {
+ return
+}
+
+# The line number of the dlclose call.
+set bp_line [gdb_get_line_number "Break here" $srcfile]
+
+# If the target is remote, then the library name in the bp_disabled_re
+# below will have a 'target:' prefix.
+if {[is_remote target]} {
+ set target_prefix_re "target:"
+} else {
+ set target_prefix_re ""
+}
+
+# The line emitted when GDB disables breakpoints after unloading a
+# shared library.
+set bp_disabled_re "warning: Temporarily disabling breakpoints for unloaded shared library \"$target_prefix_re[string_to_regexp $::libfile_on_target]\""
+
+# The complete regexp for when GDB stops on the line after BP_LINE,
+# assuming that GDB has disabled some breakpoints.
+set stop_after_bp_re [multi_line \
+ "^$::bp_disabled_re" \
+ "[expr $::bp_line + 1]\\s+assert \\(res == 0\\);"]
+
+# Checking that a breakpoint with multiple locations in a shared
+# library only triggers a single breakpoint modified event from
+# disable_breakpoints_in_unloaded_shlib when the shared library is
+# unloaded.
+proc_with_prefix test_bp_modified_events {} {
+ if { ![allow_python_tests] } {
+ unsupported "python support needed"
+ return
+ }
+
+ clean_restart $::binfile
+
+ if {![runto_main]} {
+ return
+ }
+
+ # If the debug information doesn't allow GDB to identify inline
+ # functions then this test isn't going to work.
+ get_debug_format
+ if { [skip_inline_frame_tests] } {
+ unsupported "skipping inline frame tests"
+ return
+ }
+
+ gdb_breakpoint $::srcfile:$::bp_line
+ gdb_continue_to_breakpoint "stop before dlclose"
+
+ gdb_breakpoint inline_func
+ set bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get b/p number"]
+
+ gdb_test_no_output "source $::pyfile" "import python scripts"
+
+ gdb_test "next" $::stop_after_bp_re
+
+ # The breakpoint should have been modified once when some of its
+ # locations are made pending after the shared library is unloaded.
+ gdb_test_multiple "python print(bp_modified_counts\[$bp_num\])" "" {
+ -re -wrap "^1" {
+ pass $gdb_test_name
+ }
+ -re -wrap "^2" {
+ # A second event occurs when the pending breakpoint is
+ # incorrectly deleted.
+ kfail gdb/32404 $gdb_test_name
+ }
+ -re -wrap "^$::decimal" {
+ fail $gdb_test_name
+ }
+ }
+}
+
+test_bp_modified_events
new file mode 100644
@@ -0,0 +1,26 @@
+/* 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/>. */
+
+static inline int __attribute__((__always_inline__))
+inline_func ()
+{
+ return 0;
+}
+
+/* Two library functions. */
+extern int foo (void);
+extern int bar (void);
new file mode 100644
@@ -0,0 +1,31 @@
+# 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/>.
+
+# Breakpoint modification events will be recorded in this dictionary.
+# The keys are the b/p numbers, and the values are the number of
+# modification events seen.
+bp_modified_counts = {}
+
+# Record breakpoint modification events into the global
+# bp_modified_counts dictionary.
+def bp_modified(bp):
+ global bp_modified_counts
+ if bp.number not in bp_modified_counts:
+ bp_modified_counts[bp.number] = 1
+ else:
+ bp_modified_counts[bp.number] += 1
+
+# Register the event handler.
+gdb.events.breakpoint_modified.connect(bp_modified)