Date: Thu, 19 Jun 2025 15:32:38 GMT From: Kyle Evans <kevans@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: ee9895e10d26 - main - kern: send parent a SIGCHLD when the debugger has detached Message-ID: <202506191532.55JFWcAE034012@gitrepo.freebsd.org>
next in thread | raw e-mail | index | archive | help
The branch main has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=ee9895e10d266b7bb0a25677aee3debdc17ab4bf commit ee9895e10d266b7bb0a25677aee3debdc17ab4bf Author: Kyle Evans <kevans@FreeBSD.org> AuthorDate: 2025-06-19 15:31:58 +0000 Commit: Kyle Evans <kevans@FreeBSD.org> CommitDate: 2025-06-19 15:32:04 +0000 kern: send parent a SIGCHLD when the debugger has detached The practical scenario that leads to this is porch(1) spawning some utility and sending it a SIGSTOP as a debugging aide. The user then attaches a debugger and walks through how some specific input is processed, then detaches to allow the script to continue. When ptrace is detached, the process resumes execution but the parent is never notified and may be stuck in wait(2) for it to continue or terminate. Other platforms seem to re-suspend the process after the debugger is detached, but neither behavior seems unreasonable. Just notifying the parent that the child has resumed is a relatively low-risk departure from our current behavior and had apparently been considered in the past, based on pre-existing comments. Move p_flag and p_xsig handling into childproc_continued(), as just sending the SIGCHLD here isn't really useful without P_CONTINUED set and the other caller already sets these up as well. Reviewed by: kib, markj Differential Revision: https://reviews.freebsd.org/D50917 --- lib/libsys/ptrace.2 | 12 +++++++- lib/libsys/wait.2 | 8 ++++-- sys/kern/kern_sig.c | 5 ++-- sys/kern/sys_process.c | 11 +++++-- tests/sys/kern/ptrace_test.c | 68 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 7 deletions(-) diff --git a/lib/libsys/ptrace.2 b/lib/libsys/ptrace.2 index 9b789a0e45b3..7aa24a3f820b 100644 --- a/lib/libsys/ptrace.2 +++ b/lib/libsys/ptrace.2 @@ -1,7 +1,7 @@ .\" $NetBSD: ptrace.2,v 1.2 1995/02/27 12:35:37 cgd Exp $ .\" .\" This file is in the public domain. -.Dd August 18, 2023 +.Dd June 19, 2025 .Dt PTRACE 2 .Os .Sh NAME @@ -473,6 +473,16 @@ This request is like PT_CONTINUE, except that it does not allow specifying an alternate place to continue execution, and after it succeeds, the traced process is no longer traced and continues execution normally. +.Pp +The parent of the traced process will be sent a +.Dv SIGCHLD +to indicate that the process has continued from a stopped state regardless of +whether the process was in a stopped state prior to the corresponding +.Dv PT_ATTACH +request. +A +.Xr wait 2 +for the traced process would indicate that it had been continued. .It Dv PT_GETREGS This request reads the traced process's machine registers into the .Do diff --git a/lib/libsys/wait.2 b/lib/libsys/wait.2 index 3c649f3dfa77..eeddf77aeac7 100644 --- a/lib/libsys/wait.2 +++ b/lib/libsys/wait.2 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd August 27, 2024 +.Dd June 19, 2025 .Dt WAIT 2 .Os .Sh NAME @@ -273,6 +273,10 @@ Report the status of selected processes that have continued from a job control stop by receiving a .Dv SIGCONT signal. +.Xr ptrace 2 +can also cause a process to be continued, when a +.Dv PT_DETACH +request is issued to detach the debugger. .It Dv WNOHANG Do not block when there are no processes wishing to report status. @@ -450,7 +454,7 @@ value: .Bl -tag -width Ds .It Fn WIFCONTINUED status True if the process has not terminated, and -has continued after a job control stop. +has continued after a job control stop or detach of a debugger. This macro can be true only if the wait call specified the .Dv WCONTINUED option. diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c index c972d0620ab2..4565abc4b540 100644 --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -2461,8 +2461,6 @@ tdsendsignal(struct proc *p, struct thread *td, int sig, ksiginfo_t *ksi) PROC_SLOCK(p); if (p->p_numthreads == p->p_suspcount) { PROC_SUNLOCK(p); - p->p_flag |= P_CONTINUED; - p->p_xsig = SIGCONT; PROC_LOCK(p->p_pptr); childproc_continued(p); PROC_UNLOCK(p->p_pptr); @@ -3778,6 +3776,9 @@ childproc_stopped(struct proc *p, int reason) void childproc_continued(struct proc *p) { + PROC_LOCK_ASSERT(p, MA_OWNED); + p->p_flag |= P_CONTINUED; + p->p_xsig = SIGCONT; childproc_jobstate(p, CLD_CONTINUED, SIGCONT); } diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c index 70f48adc2be8..c67996ad7df1 100644 --- a/sys/kern/sys_process.c +++ b/sys/kern/sys_process.c @@ -1324,8 +1324,15 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data) p->p_flag2 &= ~P2_PTRACE_FSTP; } - /* should we send SIGCHLD? */ - /* childproc_continued(p); */ + /* + * Send SIGCHLD and wakeup the parent as needed. It + * may be the case that they had stopped the child + * before it got ptraced, and now they're in the middle + * of a wait(2) for it to continue. + */ + PROC_LOCK(p->p_pptr); + childproc_continued(p); + PROC_UNLOCK(p->p_pptr); break; } diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c index c5160d066af7..d36dfe951e20 100644 --- a/tests/sys/kern/ptrace_test.c +++ b/tests/sys/kern/ptrace_test.c @@ -4523,6 +4523,73 @@ ATF_TC_BODY(ptrace__PT_ATTACH_no_EINTR, tc) ATF_REQUIRE(timespeccmp(&shm->sleep_time, &twelve_sec, <=)); } +ATF_TC_WITHOUT_HEAD(ptrace__PT_DETACH_continued); +ATF_TC_BODY(ptrace__PT_DETACH_continued, tc) +{ + char buf[256]; + pid_t debuggee, debugger; + int dpipe[2] = {-1, -1}, status; + + /* Setup the debuggee's pipe, which we'll use to let it terminate. */ + ATF_REQUIRE(pipe(dpipe) == 0); + ATF_REQUIRE((debuggee = fork()) != -1); + + if (debuggee == 0) { + ssize_t readsz; + + /* + * The debuggee will just absorb everything until the parent + * closes it. In the process, we expect it to get SIGSTOP'd, + * then ptrace(2)d and finally, it should resume after we detach + * and the parent will be notified. + */ + close(dpipe[1]); + while ((readsz = read(dpipe[0], buf, sizeof(buf))) != 0) { + if (readsz > 0 || errno == EINTR) + continue; + _exit(1); + } + + _exit(0); + } + + close(dpipe[0]); + + ATF_REQUIRE(kill(debuggee, SIGSTOP) == 0); + REQUIRE_EQ(waitpid(debuggee, &status, WUNTRACED), debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + + /* Child is stopped, enter the debugger to attach/detach. */ + ATF_REQUIRE((debugger = fork()) != -1); + if (debugger == 0) { + REQUIRE_EQ(ptrace(PT_ATTACH, debuggee, 0, 0), 0); + REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_DETACH, debuggee, 0, 0), 0); + _exit(0); + } + + REQUIRE_EQ(waitpid(debugger, &status, 0), debugger); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + REQUIRE_EQ(waitpid(debuggee, &status, WCONTINUED), debuggee); + ATF_REQUIRE(WIFCONTINUED(status)); + + /* + * Closing the pipe will trigger the debuggee to exit now that the + * child has resumed following detach. + */ + close(dpipe[1]); + + REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_trace_me); @@ -4592,6 +4659,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, ptrace__PT_SC_REMOTE_getpid); ATF_TP_ADD_TC(tp, ptrace__reap_kill_stopped); ATF_TP_ADD_TC(tp, ptrace__PT_ATTACH_no_EINTR); + ATF_TP_ADD_TC(tp, ptrace__PT_DETACH_continued); return (atf_no_error()); }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202506191532.55JFWcAE034012>