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:
parent
79ec12eeb6
commit
fb5720f7be
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user