Test for PR gdb/17511, spurious SIGTRAP after stepping into+in signal handler
authorPedro Alves <palves@redhat.com>
Tue, 28 Oct 2014 15:51:30 +0000 (15:51 +0000)
committerPedro Alves <palves@redhat.com>
Tue, 28 Oct 2014 15:51:30 +0000 (15:51 +0000)
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.

gdb/testsuite/ChangeLog
gdb/testsuite/gdb.base/sigstep.c
gdb/testsuite/gdb.base/sigstep.exp

index a64289ea009efe175fe2f2af6899fca3cf334304..cada90d15ea21248f79f6877798e357f878de651 100644 (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>
 
        * gdb.base/sigstep.exp: Use build_executable instead of
index cc691843abbf214553dfa66618c9e68984dc0ecd..38dd1fb1c2e4b4eb6d048e2a24d3a84c8c8952f6 100644 (file)
@@ -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;
index ef98b3d0e112dc0d7b383e61a07f56d5cf130eaa..08ad828f1217da3d361183559417ef6402b6575d 100644 (file)
@@ -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