From bfd09d203fe1eda11ef2c7a500ad1d21eae32bc0 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 19 Oct 2015 17:59:38 -0700 Subject: [PATCH] gdb: Improve syscall entry/return tracking on Linux 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 * 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 * gdb.base/catch-syscall.c: Include . (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 | 8 ++ gdb/linux-nat.c | 35 +++++-- gdb/testsuite/ChangeLog | 20 ++++ gdb/testsuite/gdb.base/catch-syscall.c | 10 ++ gdb/testsuite/gdb.base/catch-syscall.exp | 121 ++++++++++++++++++++--- 5 files changed, 171 insertions(+), 23 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 7e6440b6ae..d922d5a8ec 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,11 @@ +2015-10-19 Josh Stone + + * 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 * remote.c (remote_wait_as): Set rs->waiting_for_stop_reply to 0 diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index eb9f5bb91f..841ec3949c 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -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 diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index bf3cac9af3..8a8f132c58 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,23 @@ +2015-10-19 Josh Stone + + * gdb.base/catch-syscall.c: Include . + (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 * gdb.cp/inherit.exp (print g_vB, print g_vC, print g_vD, diff --git a/gdb/testsuite/gdb.base/catch-syscall.c b/gdb/testsuite/gdb.base/catch-syscall.c index 4d0131c0d7..e65d4a4906 100644 --- a/gdb/testsuite/gdb.base/catch-syscall.c +++ b/gdb/testsuite/gdb.base/catch-syscall.c @@ -11,6 +11,7 @@ #include #include #include +#include /* 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); } diff --git a/gdb/testsuite/gdb.base/catch-syscall.exp b/gdb/testsuite/gdb.base/catch-syscall.exp index 499da32226..c1cfe23cdd 100644 --- a/gdb/testsuite/gdb.base/catch-syscall.exp +++ b/gdb/testsuite/gdb.base/catch-syscall.exp @@ -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. -- 2.34.1