diff options
author | Andrew Burgess <aburgess@redhat.com> | 2024-01-24 13:52:59 +0000 |
---|---|---|
committer | Andrew Burgess <aburgess@redhat.com> | 2024-03-11 10:00:30 +0000 |
commit | 5eb2254a1d1d69980601e6530ee63e0e0e6a6c33 (patch) | |
tree | 9142751fd847bc706c769895e1cb98f939af167d /gdb/frame.c | |
parent | f215d2d8ad7bc1f80e68a962196f0e6dc02951c9 (diff) | |
download | fsf-binutils-gdb-5eb2254a1d1d69980601e6530ee63e0e0e6a6c33.zip fsf-binutils-gdb-5eb2254a1d1d69980601e6530ee63e0e0e6a6c33.tar.gz fsf-binutils-gdb-5eb2254a1d1d69980601e6530ee63e0e0e6a6c33.tar.bz2 |
gdb/unwinders: better support for $pc not saved
This started with a Red Hat bug report which can be seen here:
https://bugzilla.redhat.com/show_bug.cgi?id=1850710
The problem reported here was using GDB on GNU/Linux for S390, the
user stepped into JIT generated code. As they enter the JIT code GDB
would report 'PC not saved', and this same message would be reported
after each step/stepi.
Additionally, the user had 'set disassemble-next-line on', and once
they entered the JIT code this output was not displayed, nor were any
'display' directives displayed.
The user is not making use of the JIT plugin API to provide debug
information. But that's OK, they aren't expecting any source level
debug here, they are happy to use 'stepi', but the missing 'display'
directives are a problem, as is the constant 'PC not saved' (error)
message.
What is happening here is that as GDB is failing to find any debug
information for the JIT generated code, it is falling back on to the
S390 prologue unwinder to try and unwind frame #0. Unfortunately,
without being able to identify the function boundaries, the S390
prologue scanner can't help much, in fact, it doesn't even suggest an
arbitrary previous $pc value (some targets that use a link-register
will, by default, assume the link-register contains the previous $pc),
instead the S390 will just say, "sorry, I have no previous $pc value".
The result of this is that when GDB tries to find frame #1 we end
throwing an error from frame_unwind_pc (the 'PC not saved' error).
This error is not caught anywhere except at the top-level interpreter
loop, and so we end up skipping all the 'display' directive handling.
While thinking about this, I wondered, could I trigger the same error
using the Python Unwinder API? What happens if a Python unwinder
claims a frame, but then fails to provide a previous $pc value?
Turns out that exactly the same thing happens, which is great, as that
means we now have a way to reproduce this bug on any target. And so
the test included with this patch does just this. I have a Python
unwinder that claims a frame, but doesn't provide any previous
register values.
I then do two tests, first I stop in the claimed frame (i.e. frame #0
is the frame that can't be unwound), I perform a few steps, and check
the backtrace. And second, I stop in a child of the problem
frame (i.e. frame #1 is the frame that can't be unwound), and from
here I check the backtrace.
While all this is going on I have a 'display' directive in place, and
each time GDB stops I check that the display directive triggers.
Additionally, when checking the backtrace, I am checking that the
backtrace finishes with the message 'Backtrace stopped: frame did not
save the PC'.
As for the fix I chose to add a call to frame_unwind_pc directly to
get_prev_frame_always_1. Calling frame_unwind_pc will cache the
unwound $pc value, so this doesn't add much additional work as
immediately after the new frame_unwind_pc call, we call
get_prev_frame_maybe_check_cycle, which actually generates the
previous frame, which will always (I think) require a call to
frame_unwind_pc anyway.
The reason for adding the frame_unwind_pc call into
get_prev_frame_always_1, is that if the frame_unwind_pc call fails we
want to set the frames 'stop_reason', and get_prev_frame_always_1
seems to be the place where this is done, so I wanted to keep the new
stop_reason setting code next to all the existing stop_reason setting
code.
Additionally, once we enter get_prev_frame_maybe_check_cycle we
actually create the previous frame, then, if it turns out that the
previous frame can't be created we need to remove the frame .. this
seemed more complex than just making the check in
get_prev_frame_always_1.
With this fix in place the original S390 bug is fixed, and also the
test added in this commit, that uses the Python API, is also fixed.
Reviewed-By: Kevin Buettner <kevinb@redhat.com>
Diffstat (limited to 'gdb/frame.c')
-rw-r--r-- | gdb/frame.c | 32 |
1 files changed, 32 insertions, 0 deletions
diff --git a/gdb/frame.c b/gdb/frame.c index 5c7aae9..d3c4c96 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -2423,6 +2423,38 @@ get_prev_frame_always_1 (const frame_info_ptr &this_frame) } } + /* Ensure we can unwind the program counter of THIS_FRAME. */ + try + { + /* Calling frame_unwind_pc for the sentinel frame relies on the + current_frame being set, which at this point it might not be if we + are in the process of setting the current_frame after a stop (see + get_current_frame). + + The point of this check is to ensure that the unwinder for + THIS_FRAME can actually unwind the $pc, which we assume the + sentinel frame unwinder can always do (it's just a read from the + machine state), so we only call frame_unwind_pc for frames other + than the sentinel (level -1) frame. + + Additionally, we don't actually care about the value of the + unwound $pc, just that the call completed successfully. */ + if (this_frame->level >= 0) + frame_unwind_pc (this_frame); + } + catch (const gdb_exception_error &ex) + { + if (ex.error == NOT_AVAILABLE_ERROR || ex.error == OPTIMIZED_OUT_ERROR) + { + frame_debug_printf (" -> nullptr // no saved PC"); + this_frame->stop_reason = UNWIND_NO_SAVED_PC; + this_frame->prev = nullptr; + return nullptr; + } + + throw; + } + return get_prev_frame_maybe_check_cycle (this_frame); } |