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
This commit is contained in:
John Baldwin 2016-07-18 14:53:55 +00:00
parent d0cefbdc0d
commit fc4f075a1a
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=303001
7 changed files with 188 additions and 3 deletions

View File

@ -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 will also be present in the
.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 event for the creation of their
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

View File

@ -735,6 +735,7 @@ do_fork(struct thread *td, struct fork_req *fr, struct proc *p2, struct thread *
if (fr->fr_flags & RFPPWAIT) {
td->td_pflags |= TDP_RFPPWAIT;
td->td_rfppwait_p = p2;
td->td_dbgflags |= TDB_VFORK;
}
PROC_UNLOCK(p2);

View File

@ -242,5 +242,13 @@ syscallret(struct thread *td, int error, struct syscall_args *sa)
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);
}
}
}

View File

@ -993,7 +993,7 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
}
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, pid_t pid, void *addr, int data)
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)

View File

@ -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:

View File

@ -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 */

View File

@ -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());
}