Add ptrace(2) reporting for LWP events.

Add two new LWPINFO flags: PL_FLAG_BORN and PL_FLAG_EXITED for reporting
thread creation and destruction. Newly created threads will stop to report
PL_FLAG_BORN before returning to userland and exiting threads will stop to
report PL_FLAG_EXIT before exiting completely. Both of these events are
only enabled and reported if PT_LWP_EVENTS is enabled on a process.
This commit is contained in:
jhb 2015-12-29 23:25:26 +00:00
parent 79ec12eeb6
commit fb5720f7be
7 changed files with 264 additions and 30 deletions

View File

@ -1066,7 +1066,7 @@ fork_return(struct thread *td, struct trapframe *frame)
cv_broadcast(&p->p_dbgwait);
}
PROC_UNLOCK(p);
} else if (p->p_flag & P_TRACED) {
} else if (p->p_flag & P_TRACED || td->td_dbgflags & TDB_BORN) {
/*
* This is the start of a new thread in a traced
* process. Report a system call exit event.
@ -1074,9 +1074,10 @@ fork_return(struct thread *td, struct trapframe *frame)
PROC_LOCK(p);
td->td_dbgflags |= TDB_SCX;
_STOPEVENT(p, S_SCX, td->td_dbg_sc_code);
if ((p->p_stops & S_PT_SCX) != 0)
if ((p->p_stops & S_PT_SCX) != 0 ||
(td->td_dbgflags & TDB_BORN) != 0)
ptracestop(td, SIGTRAP);
td->td_dbgflags &= ~TDB_SCX;
td->td_dbgflags &= ~(TDB_SCX | TDB_BORN);
PROC_UNLOCK(p);
}

View File

@ -2501,7 +2501,12 @@ ptracestop(struct thread *td, int sig)
td->td_tid, p->p_pid, td->td_dbgflags, sig);
PROC_SLOCK(p);
while ((p->p_flag & P_TRACED) && (td->td_dbgflags & TDB_XSIG)) {
if (p->p_flag & P_SINGLE_EXIT) {
if (p->p_flag & P_SINGLE_EXIT &&
!(td->td_dbgflags & TDB_EXIT)) {
/*
* Ignore ptrace stops except for thread exit
* events when the process exits.
*/
td->td_dbgflags &= ~TDB_XSIG;
PROC_SUNLOCK(p);
return (sig);

View File

@ -253,6 +253,8 @@ thread_create(struct thread *td, struct rtprio *rtp,
thread_unlock(td);
if (P_SHOULDSTOP(p))
newtd->td_flags |= TDF_ASTPENDING | TDF_NEEDSUSPCHK;
if (p->p_flag2 & P2_LWP_EVENTS)
newtd->td_dbgflags |= TDB_BORN;
/*
* Copy the existing thread VM policy into the new thread.
@ -322,29 +324,54 @@ kern_thr_exit(struct thread *td)
p = td->td_proc;
rw_wlock(&tidhash_lock);
/*
* If all of the threads in a process call this routine to
* exit (e.g. all threads call pthread_exit()), exactly one
* thread should return to the caller to terminate the process
* instead of the thread.
*
* Checking p_numthreads alone is not sufficient since threads
* might be committed to terminating while the PROC_LOCK is
* dropped in either ptracestop() or while removing this thread
* from the tidhash. Instead, the p_pendingexits field holds
* the count of threads in either of those states and a thread
* is considered the "last" thread if all of the other threads
* in a process are already terminating.
*/
PROC_LOCK(p);
if (p->p_numthreads != 1) {
racct_sub(p, RACCT_NTHR, 1);
LIST_REMOVE(td, td_hash);
rw_wunlock(&tidhash_lock);
tdsigcleanup(td);
umtx_thread_exit(td);
PROC_SLOCK(p);
thread_stopped(p);
thread_exit();
/* NOTREACHED */
if (p->p_numthreads == p->p_pendingexits + 1) {
/*
* Ignore attempts to shut down last thread in the
* proc. This will actually call _exit(2) in the
* usermode trampoline when it returns.
*/
PROC_UNLOCK(p);
return (0);
}
/*
* Ignore attempts to shut down last thread in the proc. This
* will actually call _exit(2) in the usermode trampoline when
* it returns.
*/
p->p_pendingexits++;
td->td_dbgflags |= TDB_EXIT;
if (p->p_flag & P_TRACED && p->p_flag2 & P2_LWP_EVENTS)
ptracestop(td, SIGTRAP);
PROC_UNLOCK(p);
rw_wunlock(&tidhash_lock);
return (0);
tidhash_remove(td);
PROC_LOCK(p);
p->p_pendingexits--;
/*
* The check above should prevent all other threads from this
* process from exiting while the PROC_LOCK is dropped, so
* there must be at least one other thread other than the
* current thread.
*/
KASSERT(p->p_numthreads > 1, ("too few threads"));
racct_sub(p, RACCT_NTHR, 1);
tdsigcleanup(td);
umtx_thread_exit(td);
PROC_SLOCK(p);
thread_stopped(p);
thread_exit();
/* NOTREACHED */
}
int

View File

@ -710,6 +710,7 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
case PT_TO_SCX:
case PT_SYSCALL:
case PT_FOLLOW_FORK:
case PT_LWP_EVENTS:
case PT_DETACH:
sx_xlock(&proctree_lock);
proctree_locked = 1;
@ -948,6 +949,16 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
p->p_flag &= ~P_FOLLOWFORK;
break;
case PT_LWP_EVENTS:
CTR3(KTR_PTRACE, "PT_LWP_EVENTS: pid %d %s -> %s", p->p_pid,
p->p_flag2 & P2_LWP_EVENTS ? "enabled" : "disabled",
data ? "enabled" : "disabled");
if (data)
p->p_flag2 |= P2_LWP_EVENTS;
else
p->p_flag2 &= ~P2_LWP_EVENTS;
break;
case PT_STEP:
case PT_CONTINUE:
case PT_TO_SCE:
@ -1252,6 +1263,10 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
}
if (td2->td_dbgflags & TDB_CHILD)
pl->pl_flags |= PL_FLAG_CHILD;
if (td2->td_dbgflags & TDB_BORN)
pl->pl_flags |= PL_FLAG_BORN;
if (td2->td_dbgflags & TDB_EXIT)
pl->pl_flags |= PL_FLAG_EXITED;
pl->pl_sigmask = td2->td_sigmask;
pl->pl_siglist = td2->td_siglist;
strcpy(pl->pl_tdname, td2->td_name);

View File

@ -412,6 +412,8 @@ do { \
#define TDB_STOPATFORK 0x00000080 /* Stop at the return from fork (child
only) */
#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() */
/*
* "Private" flags kept in td_pflags:
@ -577,6 +579,7 @@ struct proc {
struct itimers *p_itimers; /* (c) POSIX interval timers. */
struct procdesc *p_procdesc; /* (e) Process descriptor, if any. */
u_int p_treeflag; /* (e) P_TREE flags */
int p_pendingexits; /* (c) Count of pending thread exits. */
/* End area that is zeroed on creation. */
#define p_endzero p_magic
@ -695,6 +698,7 @@ struct proc {
#define P2_NOTRACE 0x00000002 /* No ptrace(2) attach or coredumps. */
#define P2_NOTRACE_EXEC 0x00000004 /* Keep P2_NOPTRACE on exec(2). */
#define P2_AST_SU 0x00000008 /* Handles SU ast for kthreads. */
#define P2_LWP_EVENTS 0x00000010 /* Report LWP events via ptrace(2). */
/* Flags protected by proctree_lock, kept in p_treeflags. */
#define P_TREE_ORPHANED 0x00000001 /* Reparented, on orphan list */

View File

@ -64,6 +64,7 @@
#define PT_SYSCALL 22
#define PT_FOLLOW_FORK 23
#define PT_LWP_EVENTS 24 /* report LWP birth and exit */
#define PT_GETREGS 33 /* get general-purpose registers */
#define PT_SETREGS 34 /* set general-purpose registers */
@ -108,6 +109,8 @@ struct ptrace_lwpinfo {
#define PL_FLAG_SI 0x20 /* siginfo is valid */
#define PL_FLAG_FORKED 0x40 /* new child */
#define PL_FLAG_CHILD 0x80 /* I am from child */
#define PL_FLAG_BORN 0x100 /* new LWP */
#define PL_FLAG_EXITED 0x200 /* exiting LWP */
sigset_t pl_sigmask; /* LWP signal mask */
sigset_t pl_siglist; /* LWP pending signal */
struct __siginfo pl_siginfo; /* siginfo for signal */

View File

@ -1094,6 +1094,16 @@ simple_thread(void *arg __unused)
pthread_exit(NULL);
}
static __dead2 void
simple_thread_main(void)
{
pthread_t thread;
CHILD_REQUIRE(pthread_create(&thread, NULL, simple_thread, NULL) == 0);
CHILD_REQUIRE(pthread_join(thread, NULL) == 0);
exit(1);
}
/*
* Verify that pl_syscall_code in struct ptrace_lwpinfo for a new
* thread reports the correct value.
@ -1108,14 +1118,8 @@ ATF_TC_BODY(ptrace__new_child_pl_syscall_code_thread, tc)
ATF_REQUIRE((fpid = fork()) != -1);
if (fpid == 0) {
pthread_t thread;
trace_me();
CHILD_REQUIRE(pthread_create(&thread, NULL, simple_thread,
NULL) == 0);
CHILD_REQUIRE(pthread_join(thread, NULL) == 0);
exit(1);
simple_thread_main();
}
/* The first wait() should report the stop from SIGSTOP. */
@ -1178,6 +1182,179 @@ ATF_TC_BODY(ptrace__new_child_pl_syscall_code_thread, tc)
ATF_REQUIRE(errno == ECHILD);
}
/*
* Verify that the expected LWP events are reported for a child thread.
*/
ATF_TC_WITHOUT_HEAD(ptrace__lwp_events);
ATF_TC_BODY(ptrace__lwp_events, tc)
{
struct ptrace_lwpinfo pl;
pid_t fpid, wpid;
lwpid_t lwps[2];
int status;
ATF_REQUIRE((fpid = fork()) != -1);
if (fpid == 0) {
trace_me();
simple_thread_main();
}
/* 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_LWPINFO, wpid, (caddr_t)&pl,
sizeof(pl)) != -1);
lwps[0] = pl.pl_lwpid;
ATF_REQUIRE(ptrace(PT_LWP_EVENTS, wpid, NULL, 1) == 0);
/* Continue the child ignoring the SIGSTOP. */
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/* The first event should be for the child thread's birth. */
wpid = waitpid(fpid, &status, 0);
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_BORN | PL_FLAG_SCX)) ==
(PL_FLAG_BORN | PL_FLAG_SCX));
ATF_REQUIRE(pl.pl_lwpid != lwps[0]);
lwps[1] = pl.pl_lwpid;
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/* The next event should be for the child thread's death. */
wpid = waitpid(fpid, &status, 0);
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_EXITED | PL_FLAG_SCE)) ==
(PL_FLAG_EXITED | PL_FLAG_SCE));
ATF_REQUIRE(pl.pl_lwpid == lwps[1]);
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/* The last event should be for the child process's exit. */
wpid = waitpid(fpid, &status, 0);
ATF_REQUIRE(WIFEXITED(status));
ATF_REQUIRE(WEXITSTATUS(status) == 1);
wpid = wait(&status);
ATF_REQUIRE(wpid == -1);
ATF_REQUIRE(errno == ECHILD);
}
static void *
exec_thread(void *arg __unused)
{
execl("/usr/bin/true", "true", NULL);
exit(127);
}
static __dead2 void
exec_thread_main(void)
{
pthread_t thread;
CHILD_REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
for (;;)
sleep(60);
exit(1);
}
/*
* Verify that the expected LWP events are reported for a multithreaded
* process that calls execve(2).
*/
ATF_TC_WITHOUT_HEAD(ptrace__lwp_events_exec);
ATF_TC_BODY(ptrace__lwp_events_exec, tc)
{
struct ptrace_lwpinfo pl;
pid_t fpid, wpid;
lwpid_t lwps[2];
int status;
ATF_REQUIRE((fpid = fork()) != -1);
if (fpid == 0) {
trace_me();
exec_thread_main();
}
/* 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_LWPINFO, wpid, (caddr_t)&pl,
sizeof(pl)) != -1);
lwps[0] = pl.pl_lwpid;
ATF_REQUIRE(ptrace(PT_LWP_EVENTS, wpid, NULL, 1) == 0);
/* Continue the child ignoring the SIGSTOP. */
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/* The first event should be for the child thread's birth. */
wpid = waitpid(fpid, &status, 0);
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_BORN | PL_FLAG_SCX)) ==
(PL_FLAG_BORN | PL_FLAG_SCX));
ATF_REQUIRE(pl.pl_lwpid != lwps[0]);
lwps[1] = pl.pl_lwpid;
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/*
* The next event should be for the main thread's death due to
* single threading from execve().
*/
wpid = waitpid(fpid, &status, 0);
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_EXITED | PL_FLAG_SCE)) ==
(PL_FLAG_EXITED));
ATF_REQUIRE(pl.pl_lwpid == lwps[0]);
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/* The next event should be for the child process's exec. */
wpid = waitpid(fpid, &status, 0);
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_EXEC | PL_FLAG_SCX)) ==
(PL_FLAG_EXEC | PL_FLAG_SCX));
ATF_REQUIRE(pl.pl_lwpid == lwps[1]);
ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
/* The last event should be for the child process's exit. */
wpid = waitpid(fpid, &status, 0);
ATF_REQUIRE(WIFEXITED(status));
ATF_REQUIRE(WEXITSTATUS(status) == 0);
wpid = wait(&status);
ATF_REQUIRE(wpid == -1);
ATF_REQUIRE(errno == ECHILD);
}
ATF_TP_ADD_TCS(tp)
{
@ -1197,6 +1374,8 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_fork);
ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_vfork);
ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_thread);
ATF_TP_ADD_TC(tp, ptrace__lwp_events);
ATF_TP_ADD_TC(tp, ptrace__lwp_events_exec);
return (atf_no_error());
}