gdb: Improve syscall entry/return tracking on Linux
authorJosh Stone <jistone@redhat.com>
Tue, 20 Oct 2015 00:59:38 +0000 (17:59 -0700)
committerJosh Stone <jistone@redhat.com>
Tue, 20 Oct 2015 00:59:38 +0000 (17:59 -0700)
The existing logic was simply to flip syscall entry/return state when a
syscall trap was seen, and even then only with active 'catch syscall'.
That can get out of sync if 'catch syscall' is toggled at odd times.

This patch updates the entry/return state for all syscall traps,
regardless of catching state, and also updates known syscall state for
other kinds of traps.  Almost all PTRACE_EVENT stops are delivered from
the middle of a syscall, so this can act like an entry.  Every other
kind of ptrace stop is only delivered outside of syscall event pairs, so
marking them ignored ensures the next syscall trap looks like an entry.

Three new test scenarios are added to catch-syscall.exp:

- Disable 'catch syscall' from an entry to deliberately miss the return
  event, then re-enable to make sure a new entry is recognized.

- Enable 'catch syscall' for the first time from a vfork event, which is
  a PTRACE_EVENT_VFORK in the middle of the syscall.  Make sure the next
  syscall event is recognized as the return.

- Make sure entry and return are recognized for an ENOSYS syscall.  This
  is to defeat a common x86 hack that uses the pre-filled ENOSYS return
  value as a sign of being on the entry side.

gdb/ChangeLog:

2015-10-19  Josh Stone  <jistone@redhat.com>

* linux-nat.c (linux_handle_syscall_trap): Always update entry/
return state, even when not actively catching syscalls at all.
(linux_handle_extended_wait): Mark syscall_state like an entry.
(wait_lwp): Set syscall_state ignored for other traps.
(linux_nat_filter_event): Likewise.

gdb/testsuite/ChangeLog:

2015-10-19  Josh Stone  <jistone@redhat.com>

* gdb.base/catch-syscall.c: Include <sched.h>.
(unknown_syscall): New variable.
(main): Trigger a vfork and an unknown syscall.
* gdb.base/catch-syscall.exp (vfork_syscalls): New variable.
(unknown_syscall_number): Likewise.
(check_call_to_syscall): Accept an optional syscall pattern.
(check_return_from_syscall): Likewise.
(check_continue): Likewise.
(test_catch_syscall_without_args): Check for vfork and ENOSYS.
(test_catch_syscall_skipping_return): New test toggling off 'catch
syscall' to step over the syscall return, then toggling back on.
(test_catch_syscall_mid_vfork): New test turning on 'catch syscall'
during a PTRACE_EVENT_VFORK stop, in the middle of a vfork syscall.
(do_syscall_tests): Call test_catch_syscall_without_args and
test_catch_syscall_mid_vfork.
(test_catch_syscall_without_args_noxml): Check for vfork and ENOSYS.
(fill_all_syscalls_numbers): Initialize unknown_syscall_number.

gdb/ChangeLog
gdb/linux-nat.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.base/catch-syscall.c
gdb/testsuite/gdb.base/catch-syscall.exp

index 7e6440b6ae6111750d96b90e9176452290ba9e09..d922d5a8ecf09f149a0e17cdde0e20a185a752ac 100644 (file)
@@ -1,3 +1,11 @@
+2015-10-19  Josh Stone  <jistone@redhat.com>
+
+       * linux-nat.c (linux_handle_syscall_trap): Always update entry/
+       return state, even when not actively catching syscalls at all.
+       (linux_handle_extended_wait): Mark syscall_state like an entry.
+       (wait_lwp): Set syscall_state ignored for other traps.
+       (linux_nat_filter_event): Likewise.
+
 2015-10-19  Luis Machado  <lgustavo@codesourcery.com>
 
        * remote.c (remote_wait_as): Set rs->waiting_for_stop_reply to 0
index eb9f5bb91f6d81123cbf24bb7d66c14cd88e9645..841ec3949c37438dfba924d8db6b37ffc416dd29 100644 (file)
@@ -1916,17 +1916,17 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
       return 1;
     }
 
+  /* Always update the entry/return state, even if this particular
+     syscall isn't interesting to the core now.  In async mode,
+     the user could install a new catchpoint for this syscall
+     between syscall enter/return, and we'll need to know to
+     report a syscall return if that happens.  */
+  lp->syscall_state = (lp->syscall_state == TARGET_WAITKIND_SYSCALL_ENTRY
+                      ? TARGET_WAITKIND_SYSCALL_RETURN
+                      : TARGET_WAITKIND_SYSCALL_ENTRY);
+
   if (catch_syscall_enabled ())
     {
-      /* Always update the entry/return state, even if this particular
-        syscall isn't interesting to the core now.  In async mode,
-        the user could install a new catchpoint for this syscall
-        between syscall enter/return, and we'll need to know to
-        report a syscall return if that happens.  */
-      lp->syscall_state = (lp->syscall_state == TARGET_WAITKIND_SYSCALL_ENTRY
-                          ? TARGET_WAITKIND_SYSCALL_RETURN
-                          : TARGET_WAITKIND_SYSCALL_ENTRY);
-
       if (catching_syscall_number (syscall_number))
        {
          /* Alright, an event to report.  */
@@ -2006,6 +2006,11 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
   struct target_waitstatus *ourstatus = &lp->waitstatus;
   int event = linux_ptrace_get_extended_event (status);
 
+  /* All extended events we currently use are mid-syscall.  Only
+     PTRACE_EVENT_STOP is delivered more like a signal-stop, but
+     you have to be using PTRACE_SEIZE to get that.  */
+  lp->syscall_state = TARGET_WAITKIND_SYSCALL_ENTRY;
+
   if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK
       || event == PTRACE_EVENT_CLONE)
     {
@@ -2324,6 +2329,12 @@ wait_lwp (struct lwp_info *lp)
       if (linux_handle_syscall_trap (lp, 1))
        return wait_lwp (lp);
     }
+  else
+    {
+      /* Almost all other ptrace-stops are known to be outside of system
+        calls, with further exceptions in linux_handle_extended_wait.  */
+      lp->syscall_state = TARGET_WAITKIND_IGNORE;
+    }
 
   /* Handle GNU/Linux's extended waitstatus for trace events.  */
   if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP
@@ -3126,6 +3137,12 @@ linux_nat_filter_event (int lwpid, int status)
       if (linux_handle_syscall_trap (lp, 0))
        return NULL;
     }
+  else
+    {
+      /* Almost all other ptrace-stops are known to be outside of system
+        calls, with further exceptions in linux_handle_extended_wait.  */
+      lp->syscall_state = TARGET_WAITKIND_IGNORE;
+    }
 
   /* Handle GNU/Linux's extended waitstatus for trace events.  */
   if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP
index bf3cac9af391cfc07c80a6bb4817ee30fa2efb36..8a8f132c58f3b66acfb3bc7c37f0252477950885 100644 (file)
@@ -1,3 +1,23 @@
+2015-10-19  Josh Stone  <jistone@redhat.com>
+
+       * gdb.base/catch-syscall.c: Include <sched.h>.
+       (unknown_syscall): New variable.
+       (main): Trigger a vfork and an unknown syscall.
+       * gdb.base/catch-syscall.exp (vfork_syscalls): New variable.
+       (unknown_syscall_number): Likewise.
+       (check_call_to_syscall): Accept an optional syscall pattern.
+       (check_return_from_syscall): Likewise.
+       (check_continue): Likewise.
+       (test_catch_syscall_without_args): Check for vfork and ENOSYS.
+       (test_catch_syscall_skipping_return): New test toggling off 'catch
+       syscall' to step over the syscall return, then toggling back on.
+       (test_catch_syscall_mid_vfork): New test turning on 'catch syscall'
+       during a PTRACE_EVENT_VFORK stop, in the middle of a vfork syscall.
+       (do_syscall_tests): Call test_catch_syscall_without_args and
+       test_catch_syscall_mid_vfork.
+       (test_catch_syscall_without_args_noxml): Check for vfork and ENOSYS.
+       (fill_all_syscalls_numbers): Initialize unknown_syscall_number.
+
 2015-10-19  Andrew Stubbs  <ams@codesourcery.com>
 
        * gdb.cp/inherit.exp (print g_vB, print g_vC, print g_vD,
index 4d0131c0d7338085ae54883e0cb6e7959a044452..e65d4a4906c3529bde5d37a432316030683e6e54 100644 (file)
@@ -11,6 +11,7 @@
 #include <sys/syscall.h>
 #include <fcntl.h>
 #include <sys/stat.h>
+#include <sched.h>
 
 /* These are the syscalls numbers used by the test.  */
 
@@ -27,6 +28,7 @@ int pipe_syscall = SYS_pipe;
 int pipe2_syscall = SYS_pipe2;
 #endif
 int write_syscall = SYS_write;
+int unknown_syscall = 123456789;
 int exit_group_syscall = SYS_exit_group;
 
 int
@@ -47,6 +49,14 @@ main (void)
        write (fd[1], buf1, sizeof (buf1));
        read (fd[0], buf2, sizeof (buf2));
 
+       /* Test vfork-event interactions.  Child exits immediately.
+          (Plain fork won't work on no-mmu kernel configurations.)  */
+       if (vfork () == 0)
+         _exit (0);
+
+       /* Trigger an intentional ENOSYS.  */
+       syscall (unknown_syscall);
+
        /* The last syscall.  Do not change this.  */
        _exit (0);
 }
index 499da322267c54bb1213146591174257c423e1bb..c1cfe23cdddbf6d45b461e9a1d995ea713b02b2b 100644 (file)
@@ -51,6 +51,10 @@ set all_syscalls_numbers { }
 set last_syscall "exit_group"
 set last_syscall_number { }
 
+set vfork_syscalls "(vfork|clone2?)"
+
+set unknown_syscall_number { }
+
 # Internal procedure used to check if, after issuing a 'catch syscall'
 # command (without arguments), the 'info breakpoints' command displays
 # that '"any syscall"' is to be caught.
@@ -85,34 +89,45 @@ proc check_info_bp_many_syscalls { syscalls } {
     gdb_test "info breakpoints" ".*catchpoint.*keep y.*syscalls (.)?${filter_str}(.)?.*" $thistest
 }
 
-# This procedure checks if there was a call to a syscall.
-proc check_call_to_syscall { syscall } {
+# This procedure checks if there was a call to a syscall.  The optional
+# pattern can match syscalls that vary in implementation, like vfork.
+proc check_call_to_syscall { syscall { pattern "" } } {
     global decimal
 
+    if { $pattern eq "" } {
+      set pattern "${syscall}"
+    }
+
     set thistest "program has called $syscall"
-    gdb_test "continue" "Catchpoint $decimal \\(call to syscall .?${syscall}.?\\).*" $thistest
+    gdb_test "continue" "Catchpoint $decimal \\(call to syscall .?${pattern}.?\\).*" $thistest
 }
 
-# This procedure checks if the syscall returned.
-proc check_return_from_syscall { syscall } {
+# This procedure checks if the syscall returned.  The optional pattern
+# can match syscalls that vary in implementation, like vfork.
+proc check_return_from_syscall { syscall { pattern "" } } {
     global decimal
 
+    if { $pattern eq "" } {
+      set pattern "${syscall}"
+    }
+
     set thistest "syscall $syscall has returned"
-    gdb_test "continue" "Catchpoint $decimal \\(returned from syscall ${syscall}\\).*" $thistest
+    gdb_test "continue" "Catchpoint $decimal \\(returned from syscall ${pattern}\\).*" $thistest
 }
 
 # Internal procedure that performs two 'continue' commands and checks if
-# a syscall call AND return occur.
-proc check_continue { syscall } {
+# a syscall call AND return occur.  The optional pattern can match
+# syscalls that vary in implementation, like vfork.
+proc check_continue { syscall { pattern "" } } {
     # Testing if the 'continue' stops at the
     # specified syscall_name.  If it does, then it should
     # first print that the infeior has called the syscall,
     # and after print that the syscall has returned.
 
     # Testing if the inferior has called the syscall.
-    check_call_to_syscall $syscall
+    check_call_to_syscall $syscall $pattern
     # And now, that the syscall has returned.
-    check_return_from_syscall $syscall
+    check_return_from_syscall $syscall $pattern
 }
 
 # Inserts a syscall catchpoint with an argument.
@@ -154,7 +169,7 @@ proc check_for_program_end {} {
 }
 
 proc test_catch_syscall_without_args {} {
-    global all_syscalls last_syscall decimal
+    global all_syscalls last_syscall vfork_syscalls unknown_syscall_number decimal
 
     with_test_prefix "without arguments" {
        # Trying to set the syscall.
@@ -167,6 +182,12 @@ proc test_catch_syscall_without_args {} {
            check_continue $name
        }
 
+       check_continue "vfork" $vfork_syscalls
+
+       with_test_prefix "ENOSYS" {
+           check_continue $unknown_syscall_number
+       }
+
        # At last but not least, we check if the inferior has called
        # the last (exit) syscall.
        check_call_to_syscall $last_syscall
@@ -243,6 +264,64 @@ proc test_catch_syscall_restarting_inferior {} {
     }
 }
 
+proc test_catch_syscall_skipping_return {} {
+    with_test_prefix "skipping return" {
+       with_test_prefix "entry" {
+           set syscall_name "write"
+
+           insert_catch_syscall_with_arg $syscall_name
+
+           # Let's first reach the entry of the syscall.
+           check_call_to_syscall $syscall_name
+
+           # Now purposely skip the syscall return.
+           delete_breakpoints
+           gdb_test "stepi" ".*" "step over syscall return"
+       }
+
+       # With a naive entry/return toggle, gdb will still think
+       # the target is due for a syscall return.
+
+       with_test_prefix "entry/return" {
+           set syscall_name "read"
+
+           insert_catch_syscall_with_arg $syscall_name
+
+           # Check for entry first, then return.
+           check_continue $syscall_name
+
+           # Can we finish?
+           check_for_program_end
+       }
+    }
+}
+
+proc test_catch_syscall_mid_vfork {} {
+    global gdb_prompt decimal vfork_syscalls
+
+    with_test_prefix "mid-vfork" {
+       # Verify that the system supports "catch vfork".
+       gdb_test "catch vfork" "Catchpoint $decimal \\(vfork\\)" "insert first vfork catchpoint"
+       gdb_test_multiple "continue" "continue to first vfork catchpoint" {
+           -re ".*Your system does not support this type\r\nof catchpoint.*$gdb_prompt $" {
+               unsupported "continue to first vfork catchpoint"
+               return
+           }
+           -re ".*Catchpoint $decimal \\(vforked process $decimal\\).*$gdb_prompt $" {
+               pass "continue to first vfork catchpoint"
+           }
+       }
+
+       # Check that we now reach vfork return only.
+       # (The actual syscall used varies by architecture.)
+       gdb_test "catch syscall" "Catchpoint $decimal \\(any syscall\\)"
+       check_return_from_syscall "vfork" $vfork_syscalls
+
+       # Can we finish?
+       check_for_program_end
+    }
+}
+
 proc test_catch_syscall_fail_nodatadir {} {
     with_test_prefix "fail no datadir" {
        # Sanitizing.
@@ -302,10 +381,17 @@ proc do_syscall_tests {} {
     # This test should not trigger any catchpoints.
     if [runto_main] then { test_catch_syscall_with_wrong_args }
 
-    # Testing the 'catch' syscall command during a restart of
+    # Testing the 'catch syscall' command during a restart of
     # the inferior.
     if [runto_main] then { test_catch_syscall_restarting_inferior }
 
+    # Testing the 'catch syscall' command toggling off past a
+    # syscall return, then resuming entry/return as normal.
+    if [runto_main] then { test_catch_syscall_skipping_return }
+
+    # Testing the 'catch syscall' command starting mid-vfork.
+    if [runto_main] then { test_catch_syscall_mid_vfork }
+
     # Testing if the 'catch syscall' command works when switching to
     # different architectures on-the-fly (PR gdb/10737).
     if [runto_main] then { test_catch_syscall_multi_arch }
@@ -315,7 +401,7 @@ proc test_catch_syscall_without_args_noxml {} {
     with_test_prefix "without args noxml" {
        # We will need the syscall names even not using it because we
        # need to know know many syscalls are in the example file.
-       global all_syscalls last_syscall_number all_syscalls_numbers
+       global decimal all_syscalls last_syscall_number unknown_syscall_number all_syscalls_numbers
 
        delete_breakpoints
 
@@ -329,6 +415,12 @@ proc test_catch_syscall_without_args_noxml {} {
            }
        }
 
+       check_continue "vfork" $decimal
+
+       with_test_prefix "ENOSYS" {
+           check_continue $unknown_syscall_number
+       }
+
        # At last but not least, we check if the inferior has called
        # the last (exit) syscall.
        check_call_to_syscall $last_syscall_number
@@ -466,13 +558,14 @@ proc do_syscall_tests_without_xml {} {
 # This procedure fills the vector "all_syscalls_numbers" with the proper
 # numbers for the used syscalls according to the architecture.
 proc fill_all_syscalls_numbers {} {
-    global all_syscalls_numbers last_syscall_number all_syscalls
+    global all_syscalls_numbers last_syscall_number unknown_syscall_number all_syscalls
 
     foreach syscall $all_syscalls {
        lappend all_syscalls_numbers [get_integer_valueof "${syscall}_syscall" -1]
     }
 
     set last_syscall_number [get_integer_valueof "exit_group_syscall" -1]
+    set unknown_syscall_number [get_integer_valueof "unknown_syscall" -1]
 }
 
 # Set up the vector all_syscalls.