gdb: restructure disable_breakpoints_in_unloaded_shlib

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>
Approved-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Andrew Burgess
2024-08-28 17:21:53 +01:00
parent ffd09b625e
commit e9709998ff
6 changed files with 294 additions and 17 deletions

View File

@@ -8104,29 +8104,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)
{
@@ -8134,9 +8142,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);
}
}

View File

@@ -0,0 +1,30 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2024-2025 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 ();
}

View File

@@ -0,0 +1,63 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2024-2025 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;
}

View File

@@ -0,0 +1,114 @@
# Copyright 2024-2025 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

View File

@@ -0,0 +1,26 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2024-2025 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);

View File

@@ -0,0 +1,33 @@
# Copyright (C) 2024-2025 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)