]> granicus.if.org Git - strace/commitdiff
USE_SEIZE: fix detaching from stopped processes
authorDenys Vlasenko <dvlasenk@redhat.com>
Wed, 19 Jun 2013 13:31:39 +0000 (15:31 +0200)
committerDenys Vlasenko <dvlasenk@redhat.com>
Thu, 20 Jun 2013 09:23:00 +0000 (11:23 +0200)
V3: split SEIZE/!SEIZE code paths to reduce confusion.
Extensively comment every possible case.
Verified that all tests/detach* tests work in both SEIZE and !SEIZE
cases.

* strace.c (detach): If PTRACE_SEIZE API is in use, stop the tracee
using PTRACE_INTERRUPT instead of sending it a SIGSTOP.
In a subsequent waitpid loop, correctly wait and suppress SIGSTOP
on detach if PTRACE_INTERRUPT wasn't used, or wait for any ptrace
stop and detach without suppressing signals.

Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
strace.c

index 87aad485c5c5a208ecd68aec2b35fafd33aabc40..5a2f139ba65633d63ad982da7f3aecb0cfc819a9 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -733,7 +733,7 @@ static int
 detach(struct tcb *tcp)
 {
        int error;
-       int status, sigstop_expected;
+       int status, sigstop_expected, interrupt_done;
 
        if (tcp->flags & TCB_BPTSET)
                clearbpt(tcp);
@@ -750,6 +750,7 @@ detach(struct tcb *tcp)
 
        error = 0;
        sigstop_expected = 0;
+       interrupt_done = 0;
        if (tcp->flags & TCB_ATTACHED) {
                /*
                 * We attached but possibly didn't see the expected SIGSTOP.
@@ -757,7 +758,7 @@ detach(struct tcb *tcp)
                 * would be left stopped (process state T).
                 */
                sigstop_expected = (tcp->flags & TCB_IGNORE_ONE_SIGSTOP);
-               error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, 0);
+               error = ptrace(PTRACE_DETACH, tcp->pid, 0, 0);
                if (error == 0) {
                        /* On a clear day, you can see forever. */
                }
@@ -765,20 +766,46 @@ detach(struct tcb *tcp)
                        /* Shouldn't happen. */
                        perror_msg("detach: ptrace(PTRACE_DETACH, ...)");
                }
-               else if (my_tkill(tcp->pid, 0) < 0) {
+               else
+               /* ESRCH: process is either not stopped or doesn't exist. */
+               if (my_tkill(tcp->pid, 0) < 0) {
                        if (errno != ESRCH)
+                               /* Shouldn't happen. */
                                perror_msg("detach: checking sanity");
-               }
-               else if (!sigstop_expected && my_tkill(tcp->pid, SIGSTOP) < 0) {
-                       if (errno != ESRCH)
-                               perror_msg("detach: stopping child");
+                       /* else: process doesn't exist. */
                }
                else
-                       sigstop_expected = 1;
+               /* Process is not stopped. */
+               if (!sigstop_expected) {
+                       /* We need to stop it. */
+                       if (use_seize) {
+                               /*
+                                * With SEIZE, tracee can be in group-stop already.
+                                * In this state sending it another SIGSTOP does nothing.
+                                * Need to use INTERRUPT.
+                                * Testcase: trying to ^C a "strace -p <stopped_process>".
+                                */
+                               error = ptrace(PTRACE_INTERRUPT, tcp->pid, 0, 0);
+                               if (!error)
+                                       interrupt_done = 1;
+                       }
+                       else {
+                               error = my_tkill(tcp->pid, SIGSTOP);
+                               if (!error)
+                                       sigstop_expected = 1;
+                       }
+                       if (error && errno != ESRCH) {
+                               if (use_seize)
+                                       perror_msg("detach: ptrace(PTRACE_INTERRUPT, ...)");
+                               else
+                                       perror_msg("detach: stopping child");
+                       }
+               }
        }
 
-       if (sigstop_expected) {
+       if (sigstop_expected || interrupt_done) {
                for (;;) {
+                       int sig;
 #ifdef __WALL
                        if (waitpid(tcp->pid, &status, __WALL) < 0) {
                                if (errno == ECHILD) /* Already gone.  */
@@ -810,13 +837,49 @@ detach(struct tcb *tcp)
                                /* Au revoir, mon ami. */
                                break;
                        }
-                       if (WSTOPSIG(status) == SIGSTOP) {
+                       sig = WSTOPSIG(status);
+                       if (debug_flag)
+                               fprintf(stderr, "detach wait: event:%d sig:%d\n",
+                                               (unsigned)status >> 16, sig);
+                       if (sigstop_expected && sig == SIGSTOP) {
+                               /* Detach, suppressing SIGSTOP */
                                ptrace_restart(PTRACE_DETACH, tcp, 0);
                                break;
                        }
-                       error = ptrace_restart(PTRACE_CONT, tcp,
-                                       WSTOPSIG(status) == syscall_trap_sig ? 0
-                                       : WSTOPSIG(status));
+                       if (interrupt_done) {
+                               unsigned event = (unsigned)status >> 16;
+                               if (event == PTRACE_EVENT_STOP /*&& sig == SIGTRAP*/) {
+                                       /*
+                                        * sig == SIGTRAP: PTRACE_INTERRUPT stop.
+                                        * sig == other: process was already stopped
+                                        * with this stopping sig (see tests/detach-stopped).
+                                        * Looks like re-injecting this sig is not necessary
+                                        * in DETACH for the tracee to remain stopped.
+                                        */
+                                       sig = 0;
+                               }
+                               /*
+                                * PTRACE_INTERRUPT is not guaranteed to produce
+                                * the above event if other ptrace-stop is pending.
+                                * See tests/detach-sleeping testcase:
+                                * strace got SIGINT while tracee is sleeping.
+                                * We sent PTRACE_INTERRUPT.
+                                * We see syscall exit, not PTRACE_INTERRUPT stop.
+                                * We won't get PTRACE_INTERRUPT stop
+                                * if we would CONT now. Need to DETACH.
+                                */
+                               if (sig == syscall_trap_sig)
+                                       sig = 0;
+                               /* else: not sure in which case we can be here.
+                                * Signal stop? Inject it while detaching.
+                                */
+                               ptrace_restart(PTRACE_DETACH, tcp, sig);
+                               break;
+                       }
+                       if (sig == syscall_trap_sig)
+                               sig = 0;
+                       /* Can't detach just yet, may need to wait for SIGSTOP */
+                       error = ptrace_restart(PTRACE_CONT, tcp, sig);
                        if (error < 0)
                                break;
                }