gdb: handle empty locspec when printing breakpoints

For background reading, please see the previous patch, and the patch
before that!

After the last two patches, internal breakpoints can now be marked as
shlib_disabled if the library in which they are placed is unloaded.

The patch before last discusses a situation related to the
gdb.base/nostdlib.exp test, when run on a GNU/Linux glibc based system
where executables are compiled as PIE by default.

In this case it is observed that the dynamic linker will actually
report itself as unloaded (i.e. remove itself from the list of
currently loaded shared libraries).  This behaviour is likely a bug in
the dynamic linker, but this behaviour exists in released versions of
the dynamic linker, so GDB should (if the cost is not too great) be
changed to handle this situation.

This commit handles a problem with the 'maint info breakpoints'
command.

When the dynamic linker is unloaded the 'shlib event' breakpoint is
marked as shlib_disabled (i.e. placed into the pending state).  When
displaying the breakpoint in the 'maint info breakpoints' output, GDB
will try to print the locspec (location_spec *) as a string

Unfortunately, the locspec will be nullptr as the internal breakpoints
are not created via a location_spec, this means that GDB ends up
trying to call location_sepc::to_string() on a nullptr, resulting in
undefined behaviour (and a crash).

For most internal breakpoint types this is not a problem.  If we
consider bp_longjmp_master for example, if the shared library
containing a breakpoint of this type is unloaded then first GDB marks
the breakpoint as shlib_disabled, then after unloading the shared
library breakpoint_re_set is called, which will delete the internal
breakpoint, and then try to re-create it (if needed).  As a result,
the user never gets a change to run 'maint info breakpoints' on a
bp_longjmp_master breakpoint in the shlib_disabled state.

But bp_shlib_event and bp_thread_event breakpoints are not deleted and
recreated like this (see internal_breakpoint::re_set), so it is
possible, in rare cases, that we could end up trying to view one of
these breakpoint in a shlib_disabled state, and it would be nice if
GDB didn't crash as a result.

I've updated the printing code to check for and handle this case, and
I've updated the docs to mention this (rare) case.

For testing, I've extended gdb.base/nostdlib.exp to compile as
pie and nopie, and then run 'maint info breakpoints'.  If we're
running on a buggy glibc then this will trigger the crash.  I don't
know how I can trigger this problem without a buggy glibc as this
would require forcing the dynamic linker to be unloaded.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Approved-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Andrew Burgess
2024-12-05 17:18:13 +00:00
parent 4f578099f9
commit 5770f680c9
3 changed files with 72 additions and 25 deletions

View File

@@ -6424,7 +6424,28 @@ print_breakpoint_location (const breakpoint *b, const bp_location *loc)
uiout->field_stream ("at", stb);
}
else
uiout->field_string ("pending", b->locspec->to_string ());
{
/* Internal breakpoints don't have a locspec string, but can become
pending if the shared library the breakpoint is in is unloaded.
For most internal breakpoint types though, after unloading the
shared library, the breakpoint will be deleted and never recreated
(see internal_breakpoint::re_set). But for two internal
breakpoint types bp_shlib_event and bp_thread_event this is not
true. Usually we don't expect the libraries that contain these
breakpoints to ever be unloaded, but a buggy inferior might do
such a thing, in which case GDB should be prepared to handle this
case.
If these two breakpoint types become pending then there will be no
locspec string. */
gdb_assert (b->locspec != nullptr
|| (!user_breakpoint_p (b)
&& (b->type == bp_shlib_event
|| b->type == bp_thread_event)));
const char *locspec_str
= (b->locspec != nullptr ? b->locspec->to_string () : "");
uiout->field_string ("pending", locspec_str);
}
if (loc && is_breakpoint (b)
&& breakpoint_condition_evaluation_mode () == condition_evaluation_target

View File

@@ -32429,6 +32429,11 @@ by a symbol name.
If this breakpoint is pending, this field is present and holds the
text used to set the breakpoint, as entered by the user.
@value{GDBN}'s internal breakpoints (@pxref{maint info breakpoints})
can sometimes become pending too, for these breakpoints the
@var{pending} field will be empty as @value{GDBN} automatically
creates these breakpoints as shared libraries are loaded.
@item evaluated-by
Where this breakpoint's condition is evaluated, either @samp{host} or
@samp{target}.

View File

@@ -24,33 +24,54 @@ require !use_gdb_stub
# dependent on whether the system libraries are already prelinked.
# prelink: Could not set /lib64/libm-2.11.1.so owner or mode: Operation not permitted
set compile {
gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug additional_flags=-nostdlib}
}
set board [target_info name]
if [board_info $board exists mathlib] {
set mathlib [board_info $dest mathlib]
set_board_info mathlib ""
set err [eval $compile]
set_board_info mathlib $mathlib
} else {
set_board_info mathlib ""
set err [eval $compile]
unset_board_info mathlib
}
if {$err != ""} {
untested "failed to compile"
return -1
gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable $opts
}
clean_restart $binfile
foreach_with_prefix pie { "nopie" "pie" } {
# OPTS and BINFILE are used by the COMPILE string (defined above)
# when it is evaluated below.
set opts [list debug additional_flags=-nostdlib $pie]
set binfile [standard_output_file $testfile-$pie]
gdb_breakpoint "*marker"
gdb_breakpoint "*_start"
set board [target_info name]
if [board_info $board exists mathlib] {
set mathlib [board_info $dest mathlib]
set_board_info mathlib ""
set err [eval $compile]
set_board_info mathlib $mathlib
} else {
set_board_info mathlib ""
set err [eval $compile]
unset_board_info mathlib
}
if {$err != ""} {
untested "failed to compile"
return -1
}
gdb_run_cmd
clean_restart $binfile
# Breakpoint 2, Stopped due to shared library event
# _start () at ./gdb.base/nostdlib.c:20
gdb_test "" {Breakpoint [0-9]+, .*_start .*} "stop at run"
gdb_breakpoint "*marker"
gdb_breakpoint "*_start"
gdb_test "continue" {Breakpoint [0-9]+, marker .*} "continue to marker"
gdb_run_cmd
# Breakpoint 2, Stopped due to shared library event
# _start () at ./gdb.base/nostdlib.c:20
gdb_test "" {Breakpoint [0-9]+, .*_start .*} "stop at run"
gdb_test "continue" {Breakpoint [0-9]+, marker .*} "continue to marker"
# When compiling as PIE the executable will be a dynamic
# executable, the dynamic linker performs the PIE relocation.
# Some versions of glibc would (possibly due to a bug) report the
# dynamic linker as unmapped during startup, which places the
# 'shlib event' breakpoint(s) into the PENDING state.
#
# At one point trying to print these internal breakpoints in a
# PENDING state would crash GDB, so lets make sure that doesn't
# happen now. We don't really care about the exact output,
# gdb_test will spot if running this command crashes GDB, which is
# all we're really checking for.
gdb_test "maint info breakpoints" ".*"
}