strace/backport-0005-Factor-out-detach_interrupted_or_stopped-from-detach.patch
wangxiao65 c2f4a25c7a strace: fix potential deadlock during cleanup
(cherry picked from commit fb283e98eb9df90d0a98bf4c8053ea3ffa88c592)
2024-12-27 17:08:50 +08:00

180 lines
5.5 KiB
Diff

From d275fc0312255ddffa878b70da34d945b4fd8212 Mon Sep 17 00:00:00 2001
From: "Dmitry V. Levin" <ldv@strace.io>
Date: Mon, 27 Nov 2023 08:00:00 +0000
Subject: [PATCH] Factor out detach_interrupted_or_stopped() from detach()
* src/strace.c (detach_interrupted_or_stopped): New function.
(detach): Use it.
Reference: https://github.com/strace/strace/commit/d275fc0312255ddffa878b70da34d945b4fd8212
Conflict: NA
---
src/strace.c | 136 +++++++++++++++++++++++++++------------------------
1 file changed, 72 insertions(+), 64 deletions(-)
diff --git a/src/strace.c b/src/strace.c
index 385cbaa72..ef3360b95 100644
--- a/src/strace.c
+++ b/src/strace.c
@@ -1221,6 +1221,77 @@ interrupt_or_stop(struct tcb *tcp)
return false;
}
+/* Returns true if the tracee can be passed to droptcb. */
+static bool
+detach_interrupted_or_stopped(struct tcb *tcp, int status)
+{
+ if (!WIFSTOPPED(status)) {
+ /*
+ * Tracee exited or was killed by signal.
+ * We shouldn't normally reach this place:
+ * we don't want to consume exit status.
+ * Consider "strace -p PID" being ^C-ed:
+ * we want merely to detach from PID.
+ *
+ * However, we _can_ end up here if tracee
+ * was SIGKILLed.
+ */
+ return true;
+ }
+ unsigned int sig = WSTOPSIG(status);
+ debug_msg("detach wait: event:%d sig:%d",
+ (unsigned) status >> 16, sig);
+ if (use_seize) {
+ 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);
+ return true;
+ }
+ /* Note: this check has to be after use_seize check */
+ /* (else, in use_seize case SIGSTOP will be mistreated) */
+ if (sig == SIGSTOP) {
+ /* Detach, suppressing SIGSTOP */
+ ptrace_restart(PTRACE_DETACH, tcp, 0);
+ return true;
+ }
+ if (sig == syscall_trap_sig)
+ sig = 0;
+ /* Can't detach just yet, may need to wait for SIGSTOP */
+ int error = ptrace_restart(PTRACE_CONT, tcp, sig);
+ if (error < 0) {
+ /* Should not happen.
+ * Note: ptrace_restart returns 0 on ESRCH, so it's not it.
+ * ptrace_restart already emitted error message.
+ */
+ return true;
+ }
+ return false;
+}
+
/* Detach traced process.
* Never call DETACH twice on the same process as both unattached and
* attached-unstopped processes give the same ESRCH. For unattached process we
@@ -1240,7 +1311,6 @@ detach(struct tcb *tcp)
*/
for (;;) {
int status;
- unsigned int sig;
if (waitpid(tcp->pid, &status, __WALL) < 0) {
if (errno == EINTR)
continue;
@@ -1252,70 +1322,8 @@ detach(struct tcb *tcp)
perror_func_msg("waitpid(%u)", tcp->pid);
break;
}
- if (!WIFSTOPPED(status)) {
- /*
- * Tracee exited or was killed by signal.
- * We shouldn't normally reach this place:
- * we don't want to consume exit status.
- * Consider "strace -p PID" being ^C-ed:
- * we want merely to detach from PID.
- *
- * However, we _can_ end up here if tracee
- * was SIGKILLed.
- */
- break;
- }
- sig = WSTOPSIG(status);
- debug_msg("detach wait: event:%d sig:%d",
- (unsigned) status >> 16, sig);
- if (use_seize) {
- 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;
- }
- /* Note: this check has to be after use_seize check */
- /* (else, in use_seize case SIGSTOP will be mistreated) */
- if (sig == SIGSTOP) {
- /* Detach, suppressing SIGSTOP */
- ptrace_restart(PTRACE_DETACH, tcp, 0);
+ if (detach_interrupted_or_stopped(tcp, status))
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) {
- /* Should not happen.
- * Note: ptrace_restart returns 0 on ESRCH, so it's not it.
- * ptrace_restart already emitted error message.
- */
- break;
- }
}
drop:
--
2.33.0