diff options
author | Pedro Alves <palves@redhat.com> | 2014-10-28 13:42:11 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2014-10-28 16:00:06 +0000 |
commit | 7f5ef60532b466ec7a83a943f36e93e32e30eafe (patch) | |
tree | 9608af6276beaeb374817057404649bf0b3ab5aa /gdb/testsuite/gdb.base/sigstep.exp | |
parent | abbdbd03db7eea82cadbb418da733991cba91b15 (diff) | |
download | gdb-7f5ef60532b466ec7a83a943f36e93e32e30eafe.zip gdb-7f5ef60532b466ec7a83a943f36e93e32e30eafe.tar.gz gdb-7f5ef60532b466ec7a83a943f36e93e32e30eafe.tar.bz2 |
PR gdb/12623: non-stop crashes inferior, PC adjustment and 1-byte insns
TL;DR - if we step an instruction that is as long as
decr_pc_after_break (1-byte on x86) right after removing the
breakpoint at PC, in non-stop mode, adjust_pc_after_break adjusts the
PC, but it shouldn't.
In non-stop mode, when a breakpoint is removed, it is moved to the
"moribund locations" list. This is because other threads that are
running may have tripped on that breakpoint as well, and we haven't
heard about it. When a trap is reported, we check if perhaps it was
such a deleted breakpoint that caused the trap. If so, we also need
to adjust the PC (decr_pc_after_break).
Now, say that, on x86:
- a breakpoint was placed at an address where we have an instruction
of the same length as decr_pc_after_break on this arch (1 on x86).
- the breakpoint is removed, and thus put on the moribund locations
list.
- the thread is single-stepped.
As there's no breakpoint inserted at PC anymore, the single-step
actually executes the 1-byte instruction normally. GDB should _not_
adjust the PC for the resulting SIGTRAP. But, adjust_pc_after_break
confuses the step SIGTRAP reported for this single-step as being a
SIGTRAP for the moribund location of the breakpoint that used to be at
the previous PC, and so infrun applies the decr_pc_after_break
adjustment incorrectly.
The confusion comes from the special case mentioned in the comment:
static void
adjust_pc_after_break (struct execution_control_state *ecs)
{
...
As a special case, we could have hardware single-stepped a
software breakpoint. In this case (prev_pc == breakpoint_pc),
we also need to back up to the breakpoint address. */
if (thread_has_single_step_breakpoints_set (ecs->event_thread)
|| !ptid_equal (ecs->ptid, inferior_ptid)
|| !currently_stepping (ecs->event_thread)
|| (ecs->event_thread->stepped_breakpoint
&& ecs->event_thread->prev_pc == breakpoint_pc))
regcache_write_pc (regcache, breakpoint_pc);
The condition that incorrectly triggers is the
"ecs->event_thread->prev_pc == breakpoint_pc" one.
Afterwards, the next resume resume re-executes an instruction that had
already executed, which if you're lucky, results in the inferior
crashing. If you're unlucky, you'll get silent bad behavior...
The fix is to remember that we stepped a breakpoint. Turns out the
only case we step a breakpoint instruction today isn't covered by the
testsuite. It's the case of a 'handle nostop" signal arriving while a
step is in progress _and_ we have a software watchpoint, which forces
always single-stepping. This commit extends sigstep.exp to cover
that, and adds a new test for the adjust_pc_after_break issue.
Tested on x86_64 Fedora 20, native and gdbserver.
gdb/
2014-10-28 Pedro Alves <palves@redhat.com>
PR gdb/12623
* gdbthread.h (struct thread_info) <stepped_breakpoint>: New
field.
* infrun.c (resume) <stepping breakpoint instruction>: Set the
thread's stepped_breakpoint field. Skip if reverse debugging.
Add comment.
(init_thread_stepping_state, handle_signal_stop): Clear the
thread's stepped_breakpoint field.
gdb/testsuite/
2014-10-28 Pedro Alves <palves@redhat.com>
PR gdb/12623
* gdb.base/sigstep.c (no_handler): New global.
(main): If 'no_handler is true, set the signal handlers to
SIG_IGN.
* gdb.base/sigstep.exp (breakpoint_over_handler): Add
with_sw_watch and no_handler parameters. Handle them.
(top level) <stepping over handler when stopped at a breakpoint
test>: Add a test axis for testing with a software watchpoint, and
another for testing with the signal handler set to SIG_IGN.
* gdb.base/step-sw-breakpoint-adjust-pc.c: New file.
* gdb.base/step-sw-breakpoint-adjust-pc.exp: New file.
Diffstat (limited to 'gdb/testsuite/gdb.base/sigstep.exp')
-rw-r--r-- | gdb/testsuite/gdb.base/sigstep.exp | 45 |
1 files changed, 40 insertions, 5 deletions
diff --git a/gdb/testsuite/gdb.base/sigstep.exp b/gdb/testsuite/gdb.base/sigstep.exp index 08ad828..c4580a7 100644 --- a/gdb/testsuite/gdb.base/sigstep.exp +++ b/gdb/testsuite/gdb.base/sigstep.exp @@ -476,18 +476,37 @@ foreach cmd {"step" "next" "continue"} { # Try stepping when there's a signal pending, and a pre-existing # breakpoint at the current instruction, and no breakpoint in the -# handler. Should advance to the next line/instruction. - -proc breakpoint_over_handler { cmd } { +# handler. Should advance to the next line/instruction. If SW_WATCH +# is true, set a software watchpoint, which exercises stepping the +# breakpoint instruction while delivering a signal at the same time. +# If NO_HANDLER, arrange for the signal's handler be SIG_IGN, thus +# when the software watchpoint is also set, testing stepping a +# breakpoint instruction and immediately triggering the breakpoint +# (exercises adjust_pc_after_break logic). + +proc breakpoint_over_handler { cmd with_sw_watch no_handler } { global infinite_loop global clear_done - with_test_prefix "$cmd on breakpoint, skip handler" { + set prefix "$cmd on breakpoint, skip handler" + if { $with_sw_watch } { + append prefix ", with sw-watchpoint" + } + if { $no_handler } { + append prefix ", no handler" + } + + with_test_prefix "$prefix" { restart # Use the real-time itimer, as otherwize the process never gets # enough time to expire the timer. gdb_test_no_output "set itimer = itimer_real" + if {$no_handler} { + gdb_test "print no_handler = 1" " = 1" \ + "set no_handler" + } + gdb_test "break $infinite_loop" ".*" "break infinite loop" gdb_test "break $clear_done" ".*" "break clear done" @@ -498,10 +517,26 @@ proc breakpoint_over_handler { cmd } { # Make the signal pending sleep 1 + if { $with_sw_watch } { + # A watchpoint on a convenience variable is always a + # software watchpoint. + gdb_test "watch \$convenience" "Watchpoint .*: \\\$convenience" + } + + if {$no_handler} { + # With no handler, we need to set the global ourselves + # manually. + gdb_test "print done = 1" " = 1" "set done" + } + test_skip_handler $cmd } } foreach cmd {"stepi" "nexti" "step" "next" "continue"} { - breakpoint_over_handler $cmd + foreach with_sw_watch {0 1} { + foreach no_handler {0 1} { + breakpoint_over_handler $cmd $with_sw_watch $no_handler + } + } } |