From: Andrew Burgess Date: Thu, 5 Dec 2024 17:18:13 +0000 (+0000) Subject: gdb: handle empty locspec when printing breakpoints X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5770f680c91e1d905e423c3e92d7a7ebea15e375;p=thirdparty%2Fbinutils-gdb.git 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 Approved-By: Tom Tromey --- diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index b2017ef8b81..cf9cd76feb8 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -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 diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 8f1f0bb8a4e..19abd97838f 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -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}. diff --git a/gdb/testsuite/gdb.base/nostdlib.exp b/gdb/testsuite/gdb.base/nostdlib.exp index 906e627a068..7c32e87ebdb 100644 --- a/gdb/testsuite/gdb.base/nostdlib.exp +++ b/gdb/testsuite/gdb.base/nostdlib.exp @@ -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] + + 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_breakpoint "*marker" -gdb_breakpoint "*_start" + clean_restart $binfile -gdb_run_cmd + gdb_breakpoint "*marker" + gdb_breakpoint "*_start" -# 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_run_cmd -gdb_test "continue" {Breakpoint [0-9]+, marker .*} "continue to marker" + # 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" ".*" +}