[PATCHv5 7/7] gdb: handle empty locspec when printing breakpoints
Andrew Burgess
aburgess@redhat.com
Mon Dec 9 11:18:04 GMT 2024
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.
---
gdb/breakpoint.c | 23 +++++++++-
gdb/doc/gdb.texinfo | 5 +++
gdb/testsuite/gdb.base/nostdlib.exp | 69 +++++++++++++++++++----------
3 files changed, 72 insertions(+), 25 deletions(-)
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index c6bd6f68363..5fdfdea3f05 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -6406,7 +6406,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 85ac3d9aab6..ea8d6421fd6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -32290,6 +32290,11 @@
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 sometime 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" ".*"
+}
--
2.25.4
More information about the Gdb-patches
mailing list