From owner-svn-src-head@freebsd.org Mon Jul 18 14:53:57 2016 Return-Path: Delivered-To: svn-src-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 12382B9D2F9; Mon, 18 Jul 2016 14:53:57 +0000 (UTC) (envelope-from jhb@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id C439C16F2; Mon, 18 Jul 2016 14:53:56 +0000 (UTC) (envelope-from jhb@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id u6IErt4p085722; Mon, 18 Jul 2016 14:53:55 GMT (envelope-from jhb@FreeBSD.org) Received: (from jhb@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id u6IErtP6085715; Mon, 18 Jul 2016 14:53:55 GMT (envelope-from jhb@FreeBSD.org) Message-Id: <201607181453.u6IErtP6085715@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: jhb set sender to jhb@FreeBSD.org using -f From: John Baldwin Date: Mon, 18 Jul 2016 14:53:55 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r303001 - in head: lib/libc/sys sys/kern sys/sys tests/sys/kern X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.22 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 18 Jul 2016 14:53:57 -0000 Author: jhb Date: Mon Jul 18 14:53:55 2016 New Revision: 303001 URL: https://svnweb.freebsd.org/changeset/base/303001 Log: Add PTRACE_VFORK to trace vfork events. First, PL_FLAG_FORKED events now also set a PL_FLAG_VFORKED flag when the new child was created via vfork() rather than fork(). Second, a new PL_FLAG_VFORK_DONE event can now be enabled via the PTRACE_VFORK event mask. This new stop is reported after the vfork parent resumes due to the child calling exit or exec. Debuggers can use this stop to reinsert breakpoints in the vfork parent process before it resumes. Reviewed by: kib MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D7045 Modified: head/lib/libc/sys/ptrace.2 head/sys/kern/kern_fork.c head/sys/kern/subr_syscall.c head/sys/kern/sys_process.c head/sys/sys/proc.h head/sys/sys/ptrace.h head/tests/sys/kern/ptrace_test.c Modified: head/lib/libc/sys/ptrace.2 ============================================================================== --- head/lib/libc/sys/ptrace.2 Mon Jul 18 14:40:13 2016 (r303000) +++ head/lib/libc/sys/ptrace.2 Mon Jul 18 14:53:55 2016 (r303001) @@ -2,7 +2,7 @@ .\" $NetBSD: ptrace.2,v 1.2 1995/02/27 12:35:37 cgd Exp $ .\" .\" This file is in the public domain. -.Dd July 15, 2016 +.Dd July 18, 2016 .Dt PTRACE 2 .Os .Sh NAME @@ -141,6 +141,11 @@ The process ID of the new child process .Va pl_child_pid member of .Vt "struct ptrace_lwpinfo" . +If the new child process was created via +.Xr vfork 2 , +the traced process's stop will also include the +.Dv PL_FLAG_VFORKED +flag. Note that new child processes will be attached with the default tracing event mask; they do not inherit the event mask of the traced process. @@ -163,6 +168,33 @@ Note that new processes do not report an initial thread, and exiting processes do not report an event for the termination of the last thread. +.It Dv PTRACE_VFORK +Report a stop event when a parent process resumes after a +.Xr vfork 2 . +.Pp +When a thread in the traced process creates a new child process via +.Xr vfork 2 , +the stop that reports +.Dv PL_FLAG_FORKED +and +.Dv PL_FLAG_SCX +occurs just after the child process is created, +but before the thread waits for the child process to stop sharing process +memory. +If a debugger is not tracing the new child process, +it must ensure that no breakpoints are enabled in the shared process +memory before detaching from the new child process. +This means that no breakpoints are enabled in the parent process either. +.Pp +The +.Dv PTRACE_VFORK +flag enables a new stop that indicates when the new child process stops +sharing the process memory of the parent process. +A debugger can reinsert breakpoints in the parent process and resume it +in response to this event. +This event is indicated by setting the +.Dv PL_FLAG_VFORK_DONE +flag. .El .Pp The default tracing event mask when attaching to a process via @@ -491,6 +523,16 @@ is enabled. Note that this event is not reported when the last LWP in a process exits. The termination of the last thread is reported via a normal process exit event. +.It PL_FLAG_VFORKED +Indicates that the thread is returning from a call to +.Xr vfork 2 +that created a new child process. +This flag is set in addition to +.Dv PL_FLAG_FORKED . +.It PL_FLAG_VFORK_DONE +Indicates that the thread has resumed after a child process created via +.Xr vfork 2 +has stopped sharing its address space with the traced process. .El .It pl_sigmask The current signal mask of the LWP Modified: head/sys/kern/kern_fork.c ============================================================================== --- head/sys/kern/kern_fork.c Mon Jul 18 14:40:13 2016 (r303000) +++ head/sys/kern/kern_fork.c Mon Jul 18 14:53:55 2016 (r303001) @@ -735,6 +735,7 @@ do_fork(struct thread *td, struct fork_r if (fr->fr_flags & RFPPWAIT) { td->td_pflags |= TDP_RFPPWAIT; td->td_rfppwait_p = p2; + td->td_dbgflags |= TDB_VFORK; } PROC_UNLOCK(p2); Modified: head/sys/kern/subr_syscall.c ============================================================================== --- head/sys/kern/subr_syscall.c Mon Jul 18 14:40:13 2016 (r303000) +++ head/sys/kern/subr_syscall.c Mon Jul 18 14:53:55 2016 (r303001) @@ -242,5 +242,13 @@ again: cv_timedwait(&p2->p_pwait, &p2->p_mtx, hz); } PROC_UNLOCK(p2); + + if (td->td_dbgflags & TDB_VFORK) { + PROC_LOCK(p); + if (p->p_ptevents & PTRACE_VFORK) + ptracestop(td, SIGTRAP); + td->td_dbgflags &= ~TDB_VFORK; + PROC_UNLOCK(p); + } } } Modified: head/sys/kern/sys_process.c ============================================================================== --- head/sys/kern/sys_process.c Mon Jul 18 14:40:13 2016 (r303000) +++ head/sys/kern/sys_process.c Mon Jul 18 14:53:55 2016 (r303001) @@ -993,7 +993,7 @@ kern_ptrace(struct thread *td, int req, } tmp = *(int *)addr; if ((tmp & ~(PTRACE_EXEC | PTRACE_SCE | PTRACE_SCX | - PTRACE_FORK | PTRACE_LWP)) != 0) { + PTRACE_FORK | PTRACE_LWP | PTRACE_VFORK)) != 0) { error = EINVAL; break; } @@ -1303,7 +1303,11 @@ kern_ptrace(struct thread *td, int req, if (td2->td_dbgflags & TDB_FORK) { pl->pl_flags |= PL_FLAG_FORKED; pl->pl_child_pid = td2->td_dbg_forked; - } + if (td2->td_dbgflags & TDB_VFORK) + pl->pl_flags |= PL_FLAG_VFORKED; + } else if ((td2->td_dbgflags & (TDB_SCX | TDB_VFORK)) == + TDB_VFORK) + pl->pl_flags |= PL_FLAG_VFORK_DONE; if (td2->td_dbgflags & TDB_CHILD) pl->pl_flags |= PL_FLAG_CHILD; if (td2->td_dbgflags & TDB_BORN) Modified: head/sys/sys/proc.h ============================================================================== --- head/sys/sys/proc.h Mon Jul 18 14:40:13 2016 (r303000) +++ head/sys/sys/proc.h Mon Jul 18 14:53:55 2016 (r303001) @@ -422,6 +422,7 @@ do { \ #define TDB_CHILD 0x00000100 /* New child indicator for ptrace() */ #define TDB_BORN 0x00000200 /* New LWP indicator for ptrace() */ #define TDB_EXIT 0x00000400 /* Exiting LWP indicator for ptrace() */ +#define TDB_VFORK 0x00000800 /* vfork indicator for ptrace() */ /* * "Private" flags kept in td_pflags: Modified: head/sys/sys/ptrace.h ============================================================================== --- head/sys/sys/ptrace.h Mon Jul 18 14:40:13 2016 (r303000) +++ head/sys/sys/ptrace.h Mon Jul 18 14:53:55 2016 (r303001) @@ -89,6 +89,7 @@ #define PTRACE_SYSCALL (PTRACE_SCE | PTRACE_SCX) #define PTRACE_FORK 0x0008 #define PTRACE_LWP 0x0010 +#define PTRACE_VFORK 0x0020 #define PTRACE_DEFAULT (PTRACE_EXEC) @@ -124,6 +125,8 @@ struct ptrace_lwpinfo { #define PL_FLAG_CHILD 0x80 /* I am from child */ #define PL_FLAG_BORN 0x100 /* new LWP */ #define PL_FLAG_EXITED 0x200 /* exiting LWP */ +#define PL_FLAG_VFORKED 0x400 /* new child via vfork */ +#define PL_FLAG_VFORK_DONE 0x800 /* vfork parent has resumed */ sigset_t pl_sigmask; /* LWP signal mask */ sigset_t pl_siglist; /* LWP pending signal */ struct __siginfo pl_siginfo; /* siginfo for signal */ Modified: head/tests/sys/kern/ptrace_test.c ============================================================================== --- head/tests/sys/kern/ptrace_test.c Mon Jul 18 14:40:13 2016 (r303000) +++ head/tests/sys/kern/ptrace_test.c Mon Jul 18 14:53:55 2016 (r303001) @@ -1549,6 +1549,130 @@ ATF_TC_BODY(ptrace__event_mask, tc) ATF_REQUIRE(errno == ECHILD); } +/* + * Verify that the expected ptrace events are reported for PTRACE_VFORK. + */ +ATF_TC_WITHOUT_HEAD(ptrace__ptrace_vfork); +ATF_TC_BODY(ptrace__ptrace_vfork, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(true); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(wpid == fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + events |= PTRACE_VFORK; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) != -1); + + /* The next event should report the end of the vfork. */ + wpid = wait(&status); + ATF_REQUIRE(wpid == fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP); + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & PL_FLAG_VFORK_DONE) != 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) != -1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == fpid); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + +ATF_TC_WITHOUT_HEAD(ptrace__ptrace_vfork_follow); +ATF_TC_BODY(ptrace__ptrace_vfork_follow, tc) +{ + struct ptrace_lwpinfo pl[2]; + pid_t children[2], fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(true); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, children[0], (caddr_t)&events, + sizeof(events)) == 0); + events |= PTRACE_FORK | PTRACE_VFORK; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, children[0], (caddr_t)&events, + sizeof(events)) == 0); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + /* Wait for both halves of the fork event to get reported. */ + children[1] = handle_fork_events(children[0], pl); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_VFORKED) != 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1); + + /* + * The child can't exit until the grandchild reports status, so the + * grandchild should report its exit first to the debugger. + */ + wpid = waitpid(children[1], &status, 0); + ATF_REQUIRE(wpid == children[1]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 2); + + /* + * The child should report it's vfork() completion before it + * exits. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP); + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl[0], sizeof(pl[0])) != + -1); + ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_VFORK_DONE) != 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + ATF_TP_ADD_TCS(tp) { @@ -1574,6 +1698,8 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, ptrace__ptrace_exec_disable); ATF_TP_ADD_TC(tp, ptrace__ptrace_exec_enable); ATF_TP_ADD_TC(tp, ptrace__event_mask); + ATF_TP_ADD_TC(tp, ptrace__ptrace_vfork); + ATF_TP_ADD_TC(tp, ptrace__ptrace_vfork_follow); return (atf_no_error()); }