From abbdbd03db7eea82cadbb418da733991cba91b15 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Tue, 28 Oct 2014 15:51:30 +0000 Subject: [PATCH] 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 #2 main () at si-handler.c:50 (gdb) set disassemble-next-line on (gdb) si 0x0000000000400632 33 } 0x0000000000400631 : 5d pop %rbp => 0x0000000000400632 : c3 retq 1: $eflags = [ PF ZF TF IF ] (gdb) => 0x0000003b36a358f0 <__restore_rt+0>: 48 c7 c0 0f 00 00 00 mov $0xf,%rax 1: $eflags = [ PF ZF TF IF ] (gdb) si => 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 : c7 05 cb 09 20 00 00 00 00 00 movl $0x0,0x2009cb(%rip) # 0x601040 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 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. --- gdb/testsuite/ChangeLog | 12 ++++ gdb/testsuite/gdb.base/sigstep.c | 5 ++ gdb/testsuite/gdb.base/sigstep.exp | 93 +++++++++++++++++++++++++----- 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index a64289ea009..cada90d15ea 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,15 @@ +2014-10-28 Pedro Alves + + 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 * gdb.base/sigstep.exp: Use build_executable instead of diff --git a/gdb/testsuite/gdb.base/sigstep.c b/gdb/testsuite/gdb.base/sigstep.c index cc691843abb..38dd1fb1c2e 100644 --- a/gdb/testsuite/gdb.base/sigstep.c +++ b/gdb/testsuite/gdb.base/sigstep.c @@ -29,7 +29,12 @@ static volatile int dummy; static void 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; /* other handler location */ } /* handler */ struct itimerval itime; diff --git a/gdb/testsuite/gdb.base/sigstep.exp b/gdb/testsuite/gdb.base/sigstep.exp index ef98b3d0e11..08ad828f121 100644 --- a/gdb/testsuite/gdb.base/sigstep.exp +++ b/gdb/testsuite/gdb.base/sigstep.exp @@ -36,6 +36,7 @@ if {[build_executable $testfile.exp $testfile $srcfile debug]} { set clear_done [gdb_get_line_number {done = 0}] 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. @@ -72,29 +73,48 @@ proc validate_backtrace {} { validate_backtrace -proc advance { cmd } { +# Goes to handler using ENTER_CMD, runs IN_HANDLER while in the signal +# hander, and then steps out of the signal handler using EXIT_CMD. + +proc advance { enter_cmd in_handler_prefix in_handler exit_cmd } { global gdb_prompt inferior_exited_re + global clear_done other_handler_location - with_test_prefix "$cmd from handler" { - restart + set prefix "$enter_cmd to handler, $in_handler_prefix in handler, $exit_cmd from handler" - gdb_test "break handler" + with_test_prefix $prefix { + restart # 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" - gdb_test_multiple "$cmd" "${test}" { + gdb_test_multiple "$exit_cmd" "${test}" { -re "Could not insert single-step breakpoint.*$gdb_prompt $" { setup_kfail gdb/8841 "sparc*-*-openbsd*" fail "$test (could not insert single-step breakpoint)" } -re "done = 1;.*${gdb_prompt} $" { - send_gdb "$cmd\n" + send_gdb "$exit_cmd\n" exp_continue -continue_timer } -re "\} .. handler .*${gdb_prompt} $" { - send_gdb "$cmd\n" + send_gdb "$exit_cmd\n" exp_continue -continue_timer } -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 } { 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. - -foreach cmd {"step" "next"} { - advance $cmd -} +# 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 +# to the mainline code. foreach cmd {"stepi" "nexti" "finish" "return"} { advancei $cmd -- 2.30.2