Test for PR gdb/17511, spurious SIGTRAP after stepping into+in signal handler

I noticed that when I single-step into a signal handler with a
pending/queued signal, the following single-steps while the program is
in the signal handler leave $eflags.TF set.  That means subsequent
continues will trap after one instruction, resulting in a spurious
SIGTRAP being reported to the user.

This is a kernel bug; I've reported it to kernel devs (turned out to
be a known bug).  I'm seeing it on x86_64 Fedora 20 (Linux
3.16.4-200.fc20.x86_64), and I was told it's still not fixed upstream.

This commit extends gdb.base/sigstep.exp to cover this use case,
xfailed.

Here's what the bug looks like:

 (gdb) start
 Temporary breakpoint 1, main () at si-handler.c:48
 48        setup ();
 (gdb) next
 50        global = 0; /* set break here */

Let's queue a signal, so we can step into the handler:

 (gdb) handle SIGUSR1
 Signal        Stop      Print   Pass to program Description
 SIGUSR1       Yes       Yes     Yes             User defined signal 1
 (gdb) queue-signal SIGUSR1

TF is not set:

 (gdb) display $eflags
 1: $eflags = [ PF ZF IF ]

Now step into the handler -- "si" does PTRACE_SINGLESTEP+SIGUSR1:

 (gdb) si
 sigusr1_handler (sig=0) at si-handler.c:31
 31      {
 1: $eflags = [ PF ZF IF ]

No TF yet.  But another single-step...

 (gdb) si
 0x0000000000400621      31      {
 1: $eflags = [ PF ZF TF IF ]

... ends up with TF left set.  This results in PTRACE_CONTINUE
trapping after each instruction is executed:

 (gdb) c
 Continuing.

 Program received signal SIGTRAP, Trace/breakpoint trap.
 0x0000000000400624 in sigusr1_handler (sig=0) at si-handler.c:31
 31      {
 1: $eflags = [ PF ZF TF IF ]

 (gdb) c
 Continuing.

 Program received signal SIGTRAP, Trace/breakpoint trap.
 sigusr1_handler (sig=10) at si-handler.c:32
 32        global = 0;
 1: $eflags = [ PF ZF TF IF ]
 (gdb)

Note that even another PTRACE_SINGLESTEP does not fix it:

 (gdb) si
 33      }
 1: $eflags = [ PF ZF TF IF ]
 (gdb)

Eventually, it gets "fixed" by the rt_sigreturn syscall, when
returning out of the handler:

 (gdb) bt
 #0  sigusr1_handler (sig=10) at si-handler.c:33
 #1  <signal handler called>
 #2  main () at si-handler.c:50
 (gdb) set disassemble-next-line on
 (gdb) si
 0x0000000000400632      33      }
    0x0000000000400631 <sigusr1_handler+17>:     5d      pop    %rbp
 => 0x0000000000400632 <sigusr1_handler+18>:     c3      retq
 1: $eflags = [ PF ZF TF IF ]
 (gdb)
 <signal handler called>
 => 0x0000003b36a358f0 <__restore_rt+0>: 48 c7 c0 0f 00 00 00    mov    $0xf,%rax
 1: $eflags = [ PF ZF TF IF ]
 (gdb) si
 <signal handler called>
 => 0x0000003b36a358f7 <__restore_rt+7>: 0f 05   syscall
 1: $eflags = [ PF ZF TF IF ]
 (gdb)
 main () at si-handler.c:50
 50        global = 0; /* set break here */
 => 0x000000000040066b <main+9>: c7 05 cb 09 20 00 00 00 00 00   movl   $0x0,0x2009cb(%rip)        # 0x601040 <global>
 1: $eflags = [ PF ZF IF ]
 (gdb)

The bug doesn't happen if we instead PTRACE_CONTINUE into the signal
handler -- e.g., set a breakpoint in the handler, queue a signal, and
"continue".

gdb/testsuite/
2014-10-28  Pedro Alves  <palves@redhat.com>

	PR gdb/17511
	* gdb.base/sigstep.c (handler): Add a few more writes to 'done'.
	* gdb.base/sigstep.exp (other_handler_location): New global.
	(advance): Support stepping into the signal handler, and running
	commands while in the handler.
	(in_handler_map): New global.
	(top level): In the advance test, add combinations for getting
	into the handler with stepping commands, and for running commands
	in the handler.  Add comment descripting the advancei tests.
This commit is contained in:
Pedro Alves 2014-10-28 15:51:30 +00:00
parent 5a4b0ccc20
commit abbdbd03db
3 changed files with 98 additions and 14 deletions

View File

@ -1,3 +1,15 @@
2014-10-28 Pedro Alves <palves@redhat.com>
PR gdb/17511
* gdb.base/sigstep.c (handler): Add a few more writes to 'done'.
* gdb.base/sigstep.exp (other_handler_location): New global.
(advance): Support stepping into the signal handler, and running
commands while in the handler.
(in_handler_map): New global.
(top level): In the advance test, add combinations for getting
into the handler with stepping commands, and for running commands
in the handler. Add comment descripting the advancei tests.
2014-10-28 Pedro Alves <palves@redhat.com> 2014-10-28 Pedro Alves <palves@redhat.com>
* gdb.base/sigstep.exp: Use build_executable instead of * gdb.base/sigstep.exp: Use build_executable instead of

View File

@ -29,7 +29,12 @@ static volatile int dummy;
static void static void
handler (int sig) handler (int sig)
{ {
/* This is more than one write so that the breakpoint location below
is more than one instruction away. */
done = 1; done = 1;
done = 1;
done = 1;
done = 1; /* other handler location */
} /* handler */ } /* handler */
struct itimerval itime; struct itimerval itime;

View File

@ -36,6 +36,7 @@ if {[build_executable $testfile.exp $testfile $srcfile debug]} {
set clear_done [gdb_get_line_number {done = 0}] set clear_done [gdb_get_line_number {done = 0}]
set infinite_loop [gdb_get_line_number {while (!done)}] set infinite_loop [gdb_get_line_number {while (!done)}]
set other_handler_location [gdb_get_line_number "other handler location"]
# Restart GDB, set a display showing $PC, and run to main. # Restart GDB, set a display showing $PC, and run to main.
@ -72,29 +73,48 @@ proc validate_backtrace {} {
validate_backtrace validate_backtrace
proc advance { cmd } { # Goes to handler using ENTER_CMD, runs IN_HANDLER while in the signal
global gdb_prompt inferior_exited_re # hander, and then steps out of the signal handler using EXIT_CMD.
with_test_prefix "$cmd from handler" { proc advance { enter_cmd in_handler_prefix in_handler exit_cmd } {
global gdb_prompt inferior_exited_re
global clear_done other_handler_location
set prefix "$enter_cmd to handler, $in_handler_prefix in handler, $exit_cmd from handler"
with_test_prefix $prefix {
restart restart
gdb_test "break handler"
# Get us into the handler # Get us into the handler
gdb_test "continue" ".* handler .*" "continue to handler" if { $enter_cmd == "continue" } {
gdb_test "break handler"
} else {
gdb_test "handle SIGALRM print pass stop"
gdb_test "handle SIGVTALRM print pass stop"
gdb_test "continue" "Program received signal.*" "continue to signal"
}
gdb_test "$enter_cmd" ".*handler .*" "$enter_cmd to handler"
delete_breakpoints
uplevel 1 $in_handler
if { $exit_cmd == "continue" } {
gdb_test "break $clear_done" ".*" "break clear done"
}
set test "leave handler" set test "leave handler"
gdb_test_multiple "$cmd" "${test}" { gdb_test_multiple "$exit_cmd" "${test}" {
-re "Could not insert single-step breakpoint.*$gdb_prompt $" { -re "Could not insert single-step breakpoint.*$gdb_prompt $" {
setup_kfail gdb/8841 "sparc*-*-openbsd*" setup_kfail gdb/8841 "sparc*-*-openbsd*"
fail "$test (could not insert single-step breakpoint)" fail "$test (could not insert single-step breakpoint)"
} }
-re "done = 1;.*${gdb_prompt} $" { -re "done = 1;.*${gdb_prompt} $" {
send_gdb "$cmd\n" send_gdb "$exit_cmd\n"
exp_continue -continue_timer exp_continue -continue_timer
} }
-re "\} .. handler .*${gdb_prompt} $" { -re "\} .. handler .*${gdb_prompt} $" {
send_gdb "$cmd\n" send_gdb "$exit_cmd\n"
exp_continue -continue_timer exp_continue -continue_timer
} }
-re "$inferior_exited_re normally.*${gdb_prompt} $" { -re "$inferior_exited_re normally.*${gdb_prompt} $" {
@ -114,6 +134,55 @@ proc advance { cmd } {
} }
} }
# Map of PREFIX => "things to do within the signal handler", for the
# advance tests.
set in_handler_map {
"nothing" {
}
"si+advance" {
# Advance to the second location in handler.
gdb_test "si" "handler.*" "si in handler"
set test "advance in handler"
gdb_test_multiple "advance $other_handler_location" $test {
-re "Program received signal SIGTRAP.*$gdb_prompt $" {
# On some versions of Linux (observed on
# 3.16.4-200.fc20.x86_64), using PTRACE_SINGLESTEP+sig
# to step into a signal handler, and then issuing
# another PTRACE_SINGLESTEP within the handler ends up
# with $eflags.TF mistakenly set, which results in
# subsequent PTRACE_CONTINUEs trapping after each
# insn.
if {$enter_cmd != "continue"} {
setup_xfail "x86_64-*-linux*" gdb/17511
}
fail "$test (spurious SIGTRAP)"
return
}
-re "other handler location.*$gdb_prompt $" {
pass $test
}
}
}
}
# Check that we can step/next/continue, etc. our way in and out of a
# signal handler. Also test that we can step, and run to a breakpoint
# within the handler.
foreach enter_cmd { "stepi" "nexti" "step" "next" "continue" } {
if { $enter_cmd != "continue" && ![can_single_step_to_signal_handler] } {
continue
}
foreach exit_cmd { "step" "next" "continue" } {
foreach {in_handler_prefix in_handler} $in_handler_map {
advance $enter_cmd $in_handler_prefix $in_handler $exit_cmd
}
}
}
proc advancei { cmd } { proc advancei { cmd } {
global gdb_prompt inferior_exited_re global gdb_prompt inferior_exited_re
@ -199,11 +268,9 @@ proc advancei { cmd } {
} }
} }
# Check that we can step/next our way out of a signal handler. # Check that we can step our way out of a signal handler, using
# commands that first step out to the signal trampoline, and then out
foreach cmd {"step" "next"} { # to the mainline code.
advance $cmd
}
foreach cmd {"stepi" "nexti" "finish" "return"} { foreach cmd {"stepi" "nexti" "finish" "return"} {
advancei $cmd advancei $cmd