From d275fc0312255ddffa878b70da34d945b4fd8212 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" 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