From 8f572e5c0f71fd6641768985855b2e0955a78f51 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Fri, 10 Apr 2015 10:55:09 +0100 Subject: Fix gdb.base/sigstep.exp with displaced stepping on software single-step targets 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 : movl $0x1,0x200943(%rip) # 0x601064 (gdb) bt #0 handler (sig=14) at /home/pedro/gdb/mygit/src/gdb/testsuite/gdb.base/sigstep.c:35 #1 #2 0x0000000000400622 in _start () (gdb) FAIL: gdb.base/sigstep.exp: displaced=on: step on breakpoint, to handler: backtrace gdb/ChangeLog: 2015-04-10 Pedro Alves * 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-10 Pedro Alves * 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 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) (limited to 'gdb/infrun.c') diff --git a/gdb/infrun.c b/gdb/infrun.c index 3fcc55f..c7567c6 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); -- cgit v1.1