[PATCH v2 01/23] Fix gdb.base/sigstep.exp with displaced stepping on software single-step targets

Pedro Alves
2015-04-07T12:49:57Z

TL;DR:

When stepping over a breakpoint with displaced stepping, the core must
be notified of all signals, otherwise the displaced step fixup code
confuses a breakpoint trap in the signal handler for the expected trap
indicating the displaced instruction was single-stepped
normally/successfully.

Detailed version:

Running sigstep.exp with displaced stepping on, against my x86
software single-step branch, I got:

FAIL: gdb.base/sigstep.exp: step on breakpoint, to handler: performing step
FAIL: gdb.base/sigstep.exp: next on breakpoint, to handler: performing next
FAIL: gdb.base/sigstep.exp: continue on breakpoint, to handler: performing continue

Turning on debug logs, we see:

(gdb) step
infrun: clear_proceed_status_thread (process 32147)
infrun: proceed (addr=0xffffffffffffffff, signal=GDB_SIGNAL_DEFAULT)
infrun: resume (step=1, signal=GDB_SIGNAL_0), trap_expected=1, current thread [process 32147] at 0x400842
displaced: stepping process 32147 now
displaced: saved 0x400622: 49 89 d1 5e 48 89 e2 48 83 e4 f0 50 54 49 c7 c0
displaced: %rip-relative addressing used.
displaced: using temp reg 2, old value 0x3615eafd37, new value 0x40084c
displaced: copy 0x400842->0x400622: c7 81 1c 08 20 00 00 00 00 00
displaced: displaced pc to 0x400622
displaced: run 0x400622: c7 81 1c 08
LLR: Preparing to resume process 32147, 0, inferior_ptid process 32147
LLR: PTRACE_CONT process 32147, 0 (resume event thread)
linux_nat_wait: [process -1], [TARGET_WNOHANG]
LLW: enter
LNW: waitpid(-1, ...) returned 32147, No child processes
LLW: waitpid 32147 received Alarm clock (stopped)
LLW: PTRACE_CONT process 32147, Alarm clock (preempt 'handle')
LNW: waitpid(-1, ...) returned 0, No child processes
LLW: exit (ignore)
sigchld
infrun: target_wait (-1.0.0, status) =
infrun: -1.0.0 [process -1],
infrun: status->kind = ignore
infrun: TARGET_WAITKIND_IGNORE
infrun: prepare_to_wait
linux_nat_wait: [process -1], [TARGET_WNOHANG]
LLW: enter
LNW: waitpid(-1, ...) returned 32147, No child processes
LLW: waitpid 32147 received Trace/breakpoint trap (stopped)
CSBB: process 32147 stopped by software breakpoint
LNW: waitpid(-1, ...) returned 0, No child processes
LLW: trap ptid is process 32147.
LLW: exit
infrun: target_wait (-1.0.0, status) =
infrun: 32147.32147.0 [process 32147],
infrun: status->kind = stopped, signal = GDB_SIGNAL_TRAP
infrun: TARGET_WAITKIND_STOPPED
displaced: restored process 32147 0x400622
displaced: fixup (0x400842, 0x400622), insn = 0xc7 0x81 ...
displaced: restoring reg 2 to 0x3615eafd37
displaced: relocated %rip from 0x400717 to 0x400937
infrun: stop_pc = 0x400937
infrun: delayed software breakpoint trap, ignoring
infrun: no line number info
infrun: stop_waiting
0x0000000000400937 in __dso_handle ()
1: x/i $pc
=> 0x400937: and %ah,0xa0d64(%rip) # 0x4a16a1
(gdb) FAIL: gdb.base/sigstep.exp: displaced=on: step on breakpoint, to handler: performing step

What should have happened is that the breakpoint hit in the signal
handler should have been presented to the user. But note that
"preempt 'handle'" -- what happened instead is that
displaced_step_fixup confused the breakpoint in the signal handler for
the expected SIGTRAP indicating the displaced instruction was
single-stepped normally/successfully.

This should be affecting all software single-step targets in the same
way.

The fix is to make sure the core sees all signals when displaced
stepping, just like we already must see all signals when doing an
stepping over a breakpoint in-line. We now get:

infrun: target_wait (-1.0.0, status) =
infrun: 570.570.0 [process 570],
infrun: status->kind = stopped, signal = GDB_SIGNAL_ALRM
infrun: TARGET_WAITKIND_STOPPED
displaced: restored process 570 0x400622
infrun: stop_pc = 0x400842
infrun: random signal (GDB_SIGNAL_ALRM)
infrun: signal arrived while stepping over breakpoint
infrun: inserting step-resume breakpoint at 0x400842
infrun: resume (step=0, signal=GDB_SIGNAL_ALRM), trap_expected=0, current thread [process 570] at 0x400842
LLR: Preparing to resume process 570, Alarm clock, inferior_ptid process 570
LLR: PTRACE_CONT process 570, Alarm clock (resume event thread)
infrun: prepare_to_wait
linux_nat_wait: [process -1], [TARGET_WNOHANG]
LLW: enter
LNW: waitpid(-1, ...) returned 0, No child processes
LLW: exit (ignore)
infrun: target_wait (-1.0.0, status) =
infrun: -1.0.0 [process -1],
infrun: status->kind = ignore
sigchld
infrun: TARGET_WAITKIND_IGNORE
infrun: prepare_to_wait
linux_nat_wait: [process -1], [TARGET_WNOHANG]
LLW: enter
LNW: waitpid(-1, ...) returned 570, No child processes
LLW: waitpid 570 received Trace/breakpoint trap (stopped)
CSBB: process 570 stopped by software breakpoint
LNW: waitpid(-1, ...) returned 0, No child processes
LLW: trap ptid is process 570.
LLW: exit
infrun: target_wait (-1.0.0, status) =
infrun: 570.570.0 [process 570],
infrun: status->kind = stopped, signal = GDB_SIGNAL_TRAP
infrun: TARGET_WAITKIND_STOPPED
infrun: stop_pc = 0x400717
infrun: BPSTAT_WHAT_STOP_NOISY
infrun: stop_waiting

Breakpoint 3, handler (sig=14) at /home/pedro/gdb/mygit/src/gdb/testsuite/gdb.base/sigstep.c:35
35 done = 1;

Hardware single-step targets already behave this way, because the
Linux backends (both native and gdbserver) always report signals to
the core if the thread was single-stepping.

As mentioned in the new comment in do_target_resume, we can't fix this
by instead making the displaced_step_fixup phase skip fixing up the PC
if the single step stopped somewhere we didn't expect. Here's what
the backtrace would look like if we did that:

Breakpoint 3, handler (sig=14) at /home/pedro/gdb/mygit/src/gdb/testsuite/gdb.base/sigstep.c:35
35 done = 1;
1: x/i $pc
=> 0x400717 <handler+7>: movl $0x1,0x200943(%rip) # 0x601064 <done>
(gdb) bt
#0 handler (sig=14) at /home/pedro/gdb/mygit/src/gdb/testsuite/gdb.base/sigstep.c:35
#1 <signal handler called>
#2 0x0000000000400622 in _start ()
(gdb) FAIL: gdb.base/sigstep.exp: displaced=on: step on breakpoint, to handler: backtrace

gdb/ChangeLog:
2015-04-07 Pedro Alves <palves@redhat.com>

* infrun.c (displaced_step_in_progress): New function.
(do_target_resume): Advise target to report all signals if
displaced stepping.

gdb/testsuite/ChangeLog:
2015-04-07 Pedro Alves <palves@redhat.com>

* gdb.base/sigstep.exp (breakpoint_to_handler)
(breakpoint_to_handler_entry): New parameter 'displaced'. Use it.
Test "backtrace" in handler.
(breakpoint_over_handler): New parameter 'displaced'. Use it.
(top level): Add new "displaced" test axis to
breakpoint_to_handler, breakpoint_to_handler_entry and
breakpoint_over_handler.
---
gdb/infrun.c | 40 +++++++++++++++---
gdb/testsuite/gdb.base/sigstep.exp | 86 ++++++++++++++++++++++++++++----------
2 files changed, 98 insertions(+), 28 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 607a6e4..a270ca9 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1465,6 +1465,20 @@ get_displaced_stepping_state (int pid)
return NULL;
}

+/* Return true if process PID has a thread doing a displaced step. */
+
+static int
+displaced_step_in_progress (int pid)
+{
+ struct displaced_step_inferior_state *displaced;
+
+ displaced = get_displaced_stepping_state (pid);
+ if (displaced != NULL && !ptid_equal (displaced->step_ptid, null_ptid))
+ return 1;
+
+ return 0;
+}
+
/* Add a new displaced stepping state for process PID to the displaced
stepping state list, or return a pointer to an already existing
entry, if it already exists. Never returns NULL. */
@@ -2047,11 +2061,27 @@ do_target_resume (ptid_t resume_ptid, int step, enum gdb_signal sig)
happens to apply to another thread. */
tp->suspend.stop_signal = GDB_SIGNAL_0;

- /* Advise target which signals may be handled silently. If we have
- removed breakpoints because we are stepping over one (in any
- thread), we need to receive all signals to avoid accidentally
- skipping a breakpoint during execution of a signal handler. */
- if (step_over_info_valid_p ())
+ /* Advise target which signals may be handled silently.
+
+ If we have removed breakpoints because we are stepping over one
+ in-line (in any thread), we need to receive all signals to avoid
+ accidentally skipping a breakpoint during execution of a signal
+ handler.
+
+ Likewise if we're displaced stepping, otherwise a trap for a
+ breakpoint in a signal handler might be confused with the
+ displaced step finishing. We don't make the displaced_step_fixup
+ step distinguish the cases instead, because:
+
+ - a backtrace while stopped in the signal handler would show the
+ scratch pad as frame older than the signal handler, instead of
+ the real mainline code.
+
+ - when the thread is later resumed, the signal handler would
+ return to the scratch pad area, which would no longer be
+ valid. */
+ if (step_over_info_valid_p ()
+ || displaced_step_in_progress (ptid_get_pid (tp->ptid)))
target_pass_signals (0, NULL);
else
target_pass_signals ((int) GDB_SIGNAL_LAST, signal_pass);
diff --git a/gdb/testsuite/gdb.base/sigstep.exp b/gdb/testsuite/gdb.base/sigstep.exp
index c4f7e91..3c9454c 100644
--- a/gdb/testsuite/gdb.base/sigstep.exp
+++ b/gdb/testsuite/gdb.base/sigstep.exp
@@ -409,13 +409,19 @@ foreach cmd {"stepi" "nexti" "step" "next" "continue"} {

# Try stepping when there's a signal pending, a pre-existing
# breakpoint at the current instruction, and a breakpoint in the
-# handler. Should advance to the signal handler.
+# handler. Should advance to the signal handler. DISPLACED indicates
+# whether to try with or without displaced stepping (to exercise the
+# different techniques of stepping over the breakpoint at the current
+# instruction).

-proc breakpoint_to_handler { cmd } {
+proc breakpoint_to_handler { displaced cmd } {
global infinite_loop

- with_test_prefix "$cmd on breakpoint, to handler" {
+ with_test_prefix "displaced=$displaced: $cmd on breakpoint, to handler" {
restart
+
+ gdb_test_no_output "set displaced-stepping $displaced"
+
# 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"
@@ -430,11 +436,21 @@ proc breakpoint_to_handler { cmd } {
sleep 1

gdb_test "$cmd" " handler .*" "performing $cmd"
+
+ # Make sure we the displaced stepping scratch pad isn't in the
+ # backtrace.
+ gdb_test_sequence "bt" "backtrace" {
+ "\[\r\n\]+.0 \[^\r\n\]* handler "
+ "\[\r\n\]+.1 .signal handler called."
+ "\[\r\n\]+.2 \[^\r\n\]* main "
+ }
}
}

-foreach cmd {"step" "next" "continue"} {
- breakpoint_to_handler $cmd
+foreach displaced {"off" "on"} {
+ foreach cmd {"step" "next" "continue"} {
+ breakpoint_to_handler $displaced $cmd
+ }
}

# Try stepping when there's a signal pending, and a breakpoint at the
@@ -449,11 +465,17 @@ foreach cmd {"step" "next" "continue"} {
# have been called by the trampoline code. This test checks that it
# is possible to stop the inferior, even at that first instruction.

-proc breakpoint_to_handler_entry { cmd } {
+# DISPLACED indicates whether to try with or without displaced
+# stepping (to exercise the different techniques of stepping over the
+# breakpoint at the current instruction).
+proc breakpoint_to_handler_entry { displaced cmd } {
global infinite_loop

- with_test_prefix "$cmd on breakpoint, to handler entry" {
+ with_test_prefix "displaced=$displaced: $cmd on breakpoint, to handler entry" {
restart
+
+ gdb_test_no_output "set displaced-stepping $displaced"
+
# 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"
@@ -468,24 +490,37 @@ proc breakpoint_to_handler_entry { cmd } {
sleep 1

gdb_test "$cmd" " handler .*" "performing $cmd"
+
+ # Make sure we the displaced stepping scratch pad isn't in the
+ # backtrace.
+ gdb_test_sequence "bt" "backtrace" {
+ "\[\r\n\]+.0 \[^\r\n\]* handler "
+ "\[\r\n\]+.1 .signal handler called."
+ "\[\r\n\]+.2 \[^\r\n\]* main "
+ }
}
}

-foreach cmd {"step" "next" "continue"} {
- breakpoint_to_handler_entry $cmd
+foreach displaced {"off" "on"} {
+ foreach cmd {"step" "next" "continue"} {
+ breakpoint_to_handler_entry $displaced $cmd
+ }
}

# 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. 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 } {
+# handler. Should advance to the next line/instruction. DISPLACED
+# indicates whether to try with or without displaced stepping (to
+# exercise the different techniques of stepping over the breakpoint at
+# the current 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 { displaced cmd with_sw_watch no_handler } {
global infinite_loop
global clear_done

@@ -497,8 +532,11 @@ proc breakpoint_over_handler { cmd with_sw_watch no_handler } {
append prefix ", no handler"
}

- with_test_prefix "$prefix" {
+ with_test_prefix "displaced=$displaced: $prefix" {
restart
+
+ gdb_test_no_output "set displaced-stepping $displaced"
+
# 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"
@@ -534,10 +572,12 @@ proc breakpoint_over_handler { cmd with_sw_watch no_handler } {
}
}

-foreach cmd {"stepi" "nexti" "step" "next" "continue"} {
- foreach with_sw_watch {0 1} {
- foreach no_handler {0 1} {
- breakpoint_over_handler $cmd $with_sw_watch $no_handler
+foreach displaced {"off" "on"} {
+ foreach cmd {"stepi" "nexti" "step" "next" "continue"} {
+ foreach with_sw_watch {0 1} {
+ foreach no_handler {0 1} {
+ breakpoint_over_handler $displaced $cmd $with_sw_watch $no_handler
+ }
}
}
}
--
1.9.3