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());
 }
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to