[PATCH v3] USE_SEIZE: fix detaching from stopped processes

Denys Vlasenko dvlasenk at redhat.com
Wed Jun 19 13:52:43 UTC 2013


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 at redhat.com>
---
 strace.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 76 insertions(+), 13 deletions(-)

diff --git a/strace.c b/strace.c
index 87aad48..5a2f139 100644
--- 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;
 		}
-- 
1.8.1.4





More information about the Strace-devel mailing list