aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/kern/kern_fork.c4
-rw-r--r--sys/kern/kern_sig.c224
-rw-r--r--sys/kern/kern_thr.c2
-rw-r--r--sys/kern/subr_syscall.c6
-rw-r--r--sys/kern/sys_process.c10
-rw-r--r--sys/sys/signalvar.h6
-rw-r--r--tests/sys/kern/Makefile1
-rw-r--r--tests/sys/kern/ptrace_test.c1120
8 files changed, 1273 insertions, 100 deletions
diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c
index 7e340a9cdc9b..92bbcd72cea3 100644
--- a/sys/kern/kern_fork.c
+++ b/sys/kern/kern_fork.c
@@ -1081,7 +1081,7 @@ fork_return(struct thread *td, struct trapframe *frame)
proc_reparent(p, dbg);
sx_xunlock(&proctree_lock);
td->td_dbgflags |= TDB_CHILD | TDB_SCX | TDB_FSTP;
- ptracestop(td, SIGSTOP);
+ ptracestop(td, SIGSTOP, NULL);
td->td_dbgflags &= ~(TDB_CHILD | TDB_SCX);
} else {
/*
@@ -1102,7 +1102,7 @@ fork_return(struct thread *td, struct trapframe *frame)
_STOPEVENT(p, S_SCX, td->td_dbg_sc_code);
if ((p->p_ptevents & PTRACE_SCX) != 0 ||
(td->td_dbgflags & TDB_BORN) != 0)
- ptracestop(td, SIGTRAP);
+ ptracestop(td, SIGTRAP, NULL);
td->td_dbgflags &= ~(TDB_SCX | TDB_BORN);
PROC_UNLOCK(p);
}
diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c
index 4d3fe7b60249..86113a0412cb 100644
--- a/sys/kern/kern_sig.c
+++ b/sys/kern/kern_sig.c
@@ -278,6 +278,7 @@ sigqueue_init(sigqueue_t *list, struct proc *p)
{
SIGEMPTYSET(list->sq_signals);
SIGEMPTYSET(list->sq_kill);
+ SIGEMPTYSET(list->sq_ptrace);
TAILQ_INIT(&list->sq_list);
list->sq_proc = p;
list->sq_flags = SQ_INIT;
@@ -301,9 +302,15 @@ sigqueue_get(sigqueue_t *sq, int signo, ksiginfo_t *si)
if (!SIGISMEMBER(sq->sq_signals, signo))
return (0);
+ if (SIGISMEMBER(sq->sq_ptrace, signo)) {
+ count++;
+ SIGDELSET(sq->sq_ptrace, signo);
+ si->ksi_flags |= KSI_PTRACE;
+ }
if (SIGISMEMBER(sq->sq_kill, signo)) {
count++;
- SIGDELSET(sq->sq_kill, signo);
+ if (count == 1)
+ SIGDELSET(sq->sq_kill, signo);
}
TAILQ_FOREACH_SAFE(ksi, &sq->sq_list, ksi_link, next) {
@@ -347,7 +354,8 @@ sigqueue_take(ksiginfo_t *ksi)
if (kp->ksi_signo == ksi->ksi_signo)
break;
}
- if (kp == NULL && !SIGISMEMBER(sq->sq_kill, ksi->ksi_signo))
+ if (kp == NULL && !SIGISMEMBER(sq->sq_kill, ksi->ksi_signo) &&
+ !SIGISMEMBER(sq->sq_ptrace, ksi->ksi_signo))
SIGDELSET(sq->sq_signals, ksi->ksi_signo);
}
@@ -360,6 +368,10 @@ sigqueue_add(sigqueue_t *sq, int signo, ksiginfo_t *si)
KASSERT(sq->sq_flags & SQ_INIT, ("sigqueue not inited"));
+ /*
+ * SIGKILL/SIGSTOP cannot be caught or masked, so take the fast path
+ * for these signals.
+ */
if (signo == SIGKILL || signo == SIGSTOP || si == NULL) {
SIGADDSET(sq->sq_kill, signo);
goto out_set_bit;
@@ -398,16 +410,19 @@ sigqueue_add(sigqueue_t *sq, int signo, ksiginfo_t *si)
ksi->ksi_sigq = sq;
}
- if ((si->ksi_flags & KSI_TRAP) != 0 ||
- (si->ksi_flags & KSI_SIGQ) == 0) {
- if (ret != 0)
+ if (ret != 0) {
+ if ((si->ksi_flags & KSI_PTRACE) != 0) {
+ SIGADDSET(sq->sq_ptrace, signo);
+ ret = 0;
+ goto out_set_bit;
+ } else if ((si->ksi_flags & KSI_TRAP) != 0 ||
+ (si->ksi_flags & KSI_SIGQ) == 0) {
SIGADDSET(sq->sq_kill, signo);
- ret = 0;
- goto out_set_bit;
- }
-
- if (ret != 0)
+ ret = 0;
+ goto out_set_bit;
+ }
return (ret);
+ }
out_set_bit:
SIGADDSET(sq->sq_signals, signo);
@@ -434,6 +449,7 @@ sigqueue_flush(sigqueue_t *sq)
SIGEMPTYSET(sq->sq_signals);
SIGEMPTYSET(sq->sq_kill);
+ SIGEMPTYSET(sq->sq_ptrace);
}
static void
@@ -466,6 +482,11 @@ sigqueue_move_set(sigqueue_t *src, sigqueue_t *dst, const sigset_t *set)
SIGSETOR(dst->sq_kill, tmp);
SIGSETNAND(src->sq_kill, tmp);
+ tmp = src->sq_ptrace;
+ SIGSETAND(tmp, *set);
+ SIGSETOR(dst->sq_ptrace, tmp);
+ SIGSETNAND(src->sq_ptrace, tmp);
+
tmp = src->sq_signals;
SIGSETAND(tmp, *set);
SIGSETOR(dst->sq_signals, tmp);
@@ -502,6 +523,7 @@ sigqueue_delete_set(sigqueue_t *sq, const sigset_t *set)
}
}
SIGSETNAND(sq->sq_kill, *set);
+ SIGSETNAND(sq->sq_ptrace, *set);
SIGSETNAND(sq->sq_signals, *set);
}
@@ -2500,69 +2522,116 @@ sig_suspend_threads(struct thread *td, struct proc *p, int sending)
return (wakeup_swapper);
}
+/*
+ * Stop the process for an event deemed interesting to the debugger. If si is
+ * non-NULL, this is a signal exchange; the new signal requested by the
+ * debugger will be returned for handling. If si is NULL, this is some other
+ * type of interesting event. The debugger may request a signal be delivered in
+ * that case as well, however it will be deferred until it can be handled.
+ */
int
-ptracestop(struct thread *td, int sig)
+ptracestop(struct thread *td, int sig, ksiginfo_t *si)
{
struct proc *p = td->td_proc;
+ struct thread *td2;
+ ksiginfo_t ksi;
+ int prop;
PROC_LOCK_ASSERT(p, MA_OWNED);
KASSERT(!(p->p_flag & P_WEXIT), ("Stopping exiting process"));
WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK,
&p->p_mtx.lock_object, "Stopping for traced signal");
- td->td_dbgflags |= TDB_XSIG;
td->td_xsig = sig;
- CTR4(KTR_PTRACE, "ptracestop: tid %d (pid %d) flags %#x sig %d",
- 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 &&
- !(td->td_dbgflags & TDB_EXIT)) {
+
+ if (si == NULL || (si->ksi_flags & KSI_PTRACE) == 0) {
+ td->td_dbgflags |= TDB_XSIG;
+ CTR4(KTR_PTRACE, "ptracestop: tid %d (pid %d) flags %#x sig %d",
+ 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_KILLED(p)) {
+ /*
+ * Ensure that, if we've been PT_KILLed, the
+ * exit status reflects that. Another thread
+ * may also be in ptracestop(), having just
+ * received the SIGKILL, but this thread was
+ * unsuspended first.
+ */
+ td->td_dbgflags &= ~TDB_XSIG;
+ td->td_xsig = SIGKILL;
+ p->p_ptevents = 0;
+ break;
+ }
+ 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 (0);
+ }
+
/*
- * Ignore ptrace stops except for thread exit
- * events when the process exits.
+ * Make wait(2) work. Ensure that right after the
+ * attach, the thread which was decided to become the
+ * leader of attach gets reported to the waiter.
+ * Otherwise, just avoid overwriting another thread's
+ * assignment to p_xthread. If another thread has
+ * already set p_xthread, the current thread will get
+ * a chance to report itself upon the next iteration.
*/
- td->td_dbgflags &= ~TDB_XSIG;
- PROC_SUNLOCK(p);
- return (sig);
+ if ((td->td_dbgflags & TDB_FSTP) != 0 ||
+ ((p->p_flag2 & P2_PTRACE_FSTP) == 0 &&
+ p->p_xthread == NULL)) {
+ p->p_xsig = sig;
+ p->p_xthread = td;
+ td->td_dbgflags &= ~TDB_FSTP;
+ p->p_flag2 &= ~P2_PTRACE_FSTP;
+ p->p_flag |= P_STOPPED_SIG | P_STOPPED_TRACE;
+ sig_suspend_threads(td, p, 0);
+ }
+ if ((td->td_dbgflags & TDB_STOPATFORK) != 0) {
+ td->td_dbgflags &= ~TDB_STOPATFORK;
+ cv_broadcast(&p->p_dbgwait);
+ }
+stopme:
+ thread_suspend_switch(td, p);
+ if (p->p_xthread == td)
+ p->p_xthread = NULL;
+ if (!(p->p_flag & P_TRACED))
+ break;
+ if (td->td_dbgflags & TDB_SUSPEND) {
+ if (p->p_flag & P_SINGLE_EXIT)
+ break;
+ goto stopme;
+ }
}
+ PROC_SUNLOCK(p);
+ }
+ if (si != NULL && sig == td->td_xsig) {
+ /* Parent wants us to take the original signal unchanged. */
+ si->ksi_flags |= KSI_HEAD;
+ if (sigqueue_add(&td->td_sigqueue, sig, si) != 0)
+ si->ksi_signo = 0;
+ } else if (td->td_xsig != 0) {
/*
- * Make wait(2) work. Ensure that right after the
- * attach, the thread which was decided to become the
- * leader of attach gets reported to the waiter.
- * Otherwise, just avoid overwriting another thread's
- * assignment to p_xthread. If another thread has
- * already set p_xthread, the current thread will get
- * a chance to report itself upon the next iteration.
+ * If parent wants us to take a new signal, then it will leave
+ * it in td->td_xsig; otherwise we just look for signals again.
*/
- if ((td->td_dbgflags & TDB_FSTP) != 0 ||
- ((p->p_flag2 & P2_PTRACE_FSTP) == 0 &&
- p->p_xthread == NULL)) {
- p->p_xsig = sig;
- p->p_xthread = td;
- td->td_dbgflags &= ~TDB_FSTP;
- p->p_flag2 &= ~P2_PTRACE_FSTP;
- p->p_flag |= P_STOPPED_SIG | P_STOPPED_TRACE;
- sig_suspend_threads(td, p, 0);
- }
- if ((td->td_dbgflags & TDB_STOPATFORK) != 0) {
- td->td_dbgflags &= ~TDB_STOPATFORK;
- cv_broadcast(&p->p_dbgwait);
- }
-stopme:
- thread_suspend_switch(td, p);
- if (p->p_xthread == td)
- p->p_xthread = NULL;
- if (!(p->p_flag & P_TRACED))
- break;
- if (td->td_dbgflags & TDB_SUSPEND) {
- if (p->p_flag & P_SINGLE_EXIT)
- break;
- goto stopme;
- }
+ ksiginfo_init(&ksi);
+ ksi.ksi_signo = td->td_xsig;
+ ksi.ksi_flags |= KSI_PTRACE;
+ prop = sigprop(td->td_xsig);
+ td2 = sigtd(p, td->td_xsig, prop);
+ tdsendsignal(p, td2, td->td_xsig, &ksi);
+ if (td != td2)
+ return (0);
}
- PROC_SUNLOCK(p);
+
return (td->td_xsig);
}
@@ -2720,7 +2789,7 @@ issignal(struct thread *td)
struct sigacts *ps;
struct sigqueue *queue;
sigset_t sigpending;
- int sig, prop, newsig;
+ int sig, prop;
p = td->td_proc;
ps = p->p_sigacts;
@@ -2783,47 +2852,18 @@ issignal(struct thread *td)
}
mtx_unlock(&ps->ps_mtx);
- newsig = ptracestop(td, sig);
+ sig = ptracestop(td, sig, &td->td_dbgksi);
mtx_lock(&ps->ps_mtx);
- if (sig != newsig) {
-
- /*
- * If parent wants us to take the signal,
- * then it will leave it in p->p_xsig;
- * otherwise we just look for signals again.
- */
- if (newsig == 0)
- continue;
- sig = newsig;
-
- /*
- * Put the new signal into td_sigqueue. If the
- * signal is being masked, look for other
- * signals.
- */
- sigqueue_add(queue, sig, NULL);
- if (SIGISMEMBER(td->td_sigmask, sig))
- continue;
- signotify(td);
- } else {
- if (td->td_dbgksi.ksi_signo != 0) {
- td->td_dbgksi.ksi_flags |= KSI_HEAD;
- if (sigqueue_add(&td->td_sigqueue, sig,
- &td->td_dbgksi) != 0)
- td->td_dbgksi.ksi_signo = 0;
- }
- if (td->td_dbgksi.ksi_signo == 0)
- sigqueue_add(&td->td_sigqueue, sig,
- NULL);
- }
-
- /*
+ /*
+ * Keep looking if the debugger discarded the signal
+ * or replaced it with a masked signal.
+ *
* If the traced bit got turned off, go back up
* to the top to rescan signals. This ensures
* that p_sig* and p_sigact are consistent.
*/
- if ((p->p_flag & P_TRACED) == 0)
+ if (sig == 0 || (p->p_flag & P_TRACED) == 0)
continue;
}
diff --git a/sys/kern/kern_thr.c b/sys/kern/kern_thr.c
index 55c4ae4007ce..f352cd6736c2 100644
--- a/sys/kern/kern_thr.c
+++ b/sys/kern/kern_thr.c
@@ -356,7 +356,7 @@ kern_thr_exit(struct thread *td)
p->p_pendingexits++;
td->td_dbgflags |= TDB_EXIT;
if (p->p_ptevents & PTRACE_LWP)
- ptracestop(td, SIGTRAP);
+ ptracestop(td, SIGTRAP, NULL);
PROC_UNLOCK(p);
tidhash_remove(td);
PROC_LOCK(p);
diff --git a/sys/kern/subr_syscall.c b/sys/kern/subr_syscall.c
index 822976edf392..2bcad3449dad 100644
--- a/sys/kern/subr_syscall.c
+++ b/sys/kern/subr_syscall.c
@@ -88,7 +88,7 @@ syscallenter(struct thread *td, struct syscall_args *sa)
td->td_dbg_sc_code = sa->code;
td->td_dbg_sc_narg = sa->narg;
if (p->p_ptevents & PTRACE_SCE)
- ptracestop((td), SIGTRAP);
+ ptracestop((td), SIGTRAP, NULL);
PROC_UNLOCK(p);
}
if (td->td_dbgflags & TDB_USERWR) {
@@ -222,7 +222,7 @@ syscallret(struct thread *td, int error, struct syscall_args *sa)
if (traced &&
((td->td_dbgflags & (TDB_FORK | TDB_EXEC)) != 0 ||
(p->p_ptevents & PTRACE_SCX) != 0))
- ptracestop(td, SIGTRAP);
+ ptracestop(td, SIGTRAP, NULL);
td->td_dbgflags &= ~(TDB_SCX | TDB_EXEC | TDB_FORK);
PROC_UNLOCK(p);
}
@@ -259,7 +259,7 @@ again:
if (td->td_dbgflags & TDB_VFORK) {
PROC_LOCK(p);
if (p->p_ptevents & PTRACE_VFORK)
- ptracestop(td, SIGTRAP);
+ ptracestop(td, SIGTRAP, NULL);
td->td_dbgflags &= ~TDB_VFORK;
PROC_UNLOCK(p);
}
diff --git a/sys/kern/sys_process.c b/sys/kern/sys_process.c
index 69a3e4b7a45a..ded874ac9819 100644
--- a/sys/kern/sys_process.c
+++ b/sys/kern/sys_process.c
@@ -1125,6 +1125,16 @@ kern_ptrace(struct thread *td, int req, pid_t pid, void *addr, int data)
td2->td_dbgflags &= ~TDB_XSIG;
td2->td_xsig = data;
+ /*
+ * P_WKILLED is insurance that a PT_KILL/SIGKILL always
+ * works immediately, even if another thread is
+ * unsuspended first and attempts to handle a different
+ * signal or if the POSIX.1b style signal queue cannot
+ * accommodate any new signals.
+ */
+ if (data == SIGKILL)
+ p->p_flag |= P_WKILLED;
+
if (req == PT_DETACH) {
FOREACH_THREAD_IN_PROC(p, td3)
td3->td_dbgflags &= ~TDB_SUSPEND;
diff --git a/sys/sys/signalvar.h b/sys/sys/signalvar.h
index a8a975f843cf..ed9c87d68a9b 100644
--- a/sys/sys/signalvar.h
+++ b/sys/sys/signalvar.h
@@ -237,13 +237,15 @@ typedef struct ksiginfo {
#define KSI_INS 0x04 /* Directly insert ksi, not the copy */
#define KSI_SIGQ 0x08 /* Generated by sigqueue, might ret EGAIN. */
#define KSI_HEAD 0x10 /* Insert into head, not tail. */
-#define KSI_COPYMASK (KSI_TRAP|KSI_SIGQ)
+#define KSI_PTRACE 0x20 /* Generated by ptrace. */
+#define KSI_COPYMASK (KSI_TRAP | KSI_SIGQ | KSI_PTRACE)
#define KSI_ONQ(ksi) ((ksi)->ksi_sigq != NULL)
typedef struct sigqueue {
sigset_t sq_signals; /* All pending signals. */
sigset_t sq_kill; /* Legacy depth 1 queue. */
+ sigset_t sq_ptrace; /* Depth 1 queue for ptrace(2). */
TAILQ_HEAD(, ksiginfo) sq_list;/* Queued signal info. */
struct proc *sq_proc;
int sq_flags;
@@ -370,7 +372,7 @@ void pgsigio(struct sigio **sigiop, int sig, int checkctty);
void pgsignal(struct pgrp *pgrp, int sig, int checkctty, ksiginfo_t *ksi);
int postsig(int sig);
void kern_psignal(struct proc *p, int sig);
-int ptracestop(struct thread *td, int sig);
+int ptracestop(struct thread *td, int sig, ksiginfo_t *si);
void sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *retmask);
struct sigacts *sigacts_alloc(void);
void sigacts_copy(struct sigacts *dest, struct sigacts *src);
diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
index 795752cd7779..de1fe1538ae3 100644
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -8,6 +8,7 @@ TESTSDIR= ${TESTSBASE}/sys/kern
ATF_TESTS_C+= kern_copyin
ATF_TESTS_C+= kern_descrip_test
ATF_TESTS_C+= ptrace_test
+TEST_METADATA.ptrace_test+= timeout="15"
ATF_TESTS_C+= reaper
PLAIN_TESTS_C+= subr_unit_test
ATF_TESTS_C+= unix_seqpacket_test
diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c
index dede0f84f4aa..4006ea0501aa 100644
--- a/tests/sys/kern/ptrace_test.c
+++ b/tests/sys/kern/ptrace_test.c
@@ -28,6 +28,9 @@
__FBSDID("$FreeBSD$");
#include <sys/types.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/time.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/sysctl.h>
@@ -35,6 +38,7 @@ __FBSDID("$FreeBSD$");
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
+#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@@ -1673,6 +1677,1107 @@ ATF_TC_BODY(ptrace__ptrace_vfork_follow, tc)
ATF_REQUIRE(errno == ECHILD);
}
+/*
+ * Verify that no more events are reported after PT_KILL except for the
+ * process exit when stopped due to a breakpoint trap.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_breakpoint);
+ATF_TC_BODY(ptrace__PT_KILL_breakpoint, tc)
+{
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ __builtin_debugtrap();
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report hitting the breakpoint. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ /* Kill the child process. */
+ ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+ /* The last wait() should report the SIGKILL. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that no more events are reported after PT_KILL except for the
+ * process exit when stopped inside of a system call.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_system_call);
+ATF_TC_BODY(ptrace__PT_KILL_system_call, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ getpid();
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP and tracing system calls. */
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report a system call entry for getpid(). */
+ 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_SCE);
+
+ /* Kill the child process. */
+ ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+ /* The last wait() should report the SIGKILL. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that no more events are reported after PT_KILL except for the
+ * process exit when killing a multithreaded process.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_threads);
+ATF_TC_BODY(ptrace__PT_KILL_threads, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ lwpid_t main_lwp;
+ 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);
+ main_lwp = 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 != main_lwp);
+
+ /* Kill the child process. */
+ ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+ /* The last wait() should report the SIGKILL. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void *
+mask_usr1_thread(void *arg)
+{
+ pthread_barrier_t *pbarrier;
+ sigset_t sigmask;
+
+ pbarrier = (pthread_barrier_t*)arg;
+
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGUSR1);
+ CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+ /* Sync up with other thread after sigmask updated. */
+ pthread_barrier_wait(pbarrier);
+
+ for (;;)
+ sleep(60);
+
+ return (NULL);
+}
+
+/*
+ * Verify that the SIGKILL from PT_KILL takes priority over other signals
+ * and prevents spurious stops due to those other signals.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_competing_signal);
+ATF_TC_BODY(ptrace__PT_KILL_competing_signal, tc)
+{
+ pid_t fpid, wpid;
+ int status;
+ cpuset_t setmask;
+ pthread_t t;
+ pthread_barrier_t barrier;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ /*
+ * Bind to one CPU so only one thread at a time will run. This
+ * test expects that the first thread created (the main thread)
+ * will be unsuspended first and will block the second thread
+ * from running.
+ */
+ CPU_ZERO(&setmask);
+ CPU_SET(0, &setmask);
+ cpusetid_t setid;
+ CHILD_REQUIRE(cpuset(&setid) == 0);
+ CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET,
+ CPU_WHICH_CPUSET, setid, sizeof(setmask), &setmask) == 0);
+
+ CHILD_REQUIRE(pthread_barrier_init(&barrier, NULL, 2) == 0);
+
+ CHILD_REQUIRE(pthread_create(&t, NULL, mask_usr1_thread,
+ (void*)&barrier) == 0);
+
+ sigset_t sigmask;
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGUSR2);
+ CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+ /* Sync up with other thread after sigmask updated. */
+ pthread_barrier_wait(&barrier);
+
+ trace_me();
+
+ for (;;)
+ sleep(60);
+
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* Send a signal that only the second thread can handle. */
+ ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+ /* The second wait() should report the SIGUSR2. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+
+ /* Send a signal that only the first thread can handle. */
+ ATF_REQUIRE(kill(fpid, SIGUSR1) == 0);
+
+ /* Replace the SIGUSR2 with a kill. */
+ ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+ /* The last wait() should report the SIGKILL (not the SIGUSR signal). */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that the SIGKILL from PT_KILL takes priority over other stop events
+ * and prevents spurious stops caused by those events.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_competing_stop);
+ATF_TC_BODY(ptrace__PT_KILL_competing_stop, tc)
+{
+ pid_t fpid, wpid;
+ int status, i;
+ cpuset_t setmask;
+ pthread_t t;
+ pthread_barrier_t barrier;
+ lwpid_t main_lwp;
+ struct ptrace_lwpinfo pl;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+
+ /*
+ * Bind to one CPU so only one thread at a time will run. This
+ * test expects that the first thread created (the main thread)
+ * will be unsuspended first and will block the second thread
+ * from running.
+ */
+ CPU_ZERO(&setmask);
+ CPU_SET(0, &setmask);
+ cpusetid_t setid;
+ CHILD_REQUIRE(cpuset(&setid) == 0);
+ CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET,
+ CPU_WHICH_CPUSET, setid, sizeof(setmask), &setmask) == 0);
+
+ CHILD_REQUIRE(pthread_barrier_init(&barrier, NULL, 2) == 0);
+
+ CHILD_REQUIRE(pthread_create(&t, NULL, mask_usr1_thread,
+ (void*)&barrier) == 0);
+
+ sigset_t sigmask;
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGUSR2);
+ CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+ /* Sync up with other thread after sigmask updated. */
+ pthread_barrier_wait(&barrier);
+
+ /* Sync up with the test before doing the getpid(). */
+ raise(SIGSTOP);
+
+ getpid();
+ exit(1);
+ }
+
+ /* 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);
+ main_lwp = pl.pl_lwpid;
+
+ /* Continue the child ignoring the SIGSTOP and tracing system calls. */
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /*
+ * Continue until child is done with setup, which is indicated with
+ * SIGSTOP. Ignore system calls in the meantime.
+ */
+ for (;;) {
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ if (WSTOPSIG(status) == SIGTRAP) {
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+ sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX));
+ } else {
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+ break;
+ }
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+ }
+
+ /* Let both threads hit their syscall entries. */
+ for (i = 0; i < 2; ++i) {
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ 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_SCE);
+
+ /*
+ * Prevent the main thread from hitting its syscall exit for
+ * now.
+ */
+ if (pl.pl_lwpid == main_lwp)
+ ATF_REQUIRE(ptrace(PT_SUSPEND, main_lwp, 0, 0) == 0);
+
+ }
+
+ /* Send a signal that only the second thread can handle. */
+ ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report the SIGUSR2. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+
+ /* Allow the main thread to try to finish its system call. */
+ ATF_REQUIRE(ptrace(PT_RESUME, main_lwp, 0, 0) == 0);
+
+ /*
+ * At this point, the main thread is in the middle of a system call and
+ * has been resumed. The second thread has taken a signal which will be
+ * replaced with a SIGKILL. We expect the main thread will get to run
+ * first. It should notice the kill request and exit accordingly and
+ * not stop for the system call exit event.
+ */
+
+ /* Replace the SIGUSR2 with a kill. */
+ ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+ /* The last wait() should report the SIGKILL (not a syscall exit). */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void
+sigusr1_handler(int sig)
+{
+
+ CHILD_REQUIRE(sig == SIGUSR1);
+ _exit(2);
+}
+
+/*
+ * Verify that even if the signal queue is full for a child process,
+ * a PT_KILL will kill the process.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_with_signal_full_sigqueue);
+ATF_TC_BODY(ptrace__PT_KILL_with_signal_full_sigqueue, tc)
+{
+ pid_t fpid, wpid;
+ int status;
+ int max_pending_per_proc;
+ size_t len;
+ int i;
+
+ ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ exit(1);
+ }
+
+ /* 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);
+
+ len = sizeof(max_pending_per_proc);
+ ATF_REQUIRE(sysctlbyname("kern.sigqueue.max_pending_per_proc",
+ &max_pending_per_proc, &len, NULL, 0) == 0);
+
+ /* Fill the signal queue. */
+ for (i = 0; i < max_pending_per_proc; ++i)
+ ATF_REQUIRE(kill(fpid, SIGUSR1) == 0);
+
+ /* Kill the child process. */
+ ATF_REQUIRE(ptrace(PT_KILL, fpid, 0, 0) == 0);
+
+ /* The last wait() should report the SIGKILL. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGKILL);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that when stopped at a system call entry, a signal can be
+ * requested with PT_CONTINUE which will be delivered once the system
+ * call is complete.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_system_call_entry);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_system_call_entry, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ getpid();
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP and tracing system calls. */
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report a system call entry for getpid(). */
+ 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_SCE);
+
+ /* Continue the child process with a signal. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ for (;;) {
+ /*
+ * The last wait() should report exit 2, i.e., a normal _exit
+ * from the signal handler. In the meantime, catch and proceed
+ * past any syscall stops.
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX));
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ } else {
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+ break;
+ }
+ }
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void
+sigusr1_counting_handler(int sig)
+{
+ static int counter = 0;
+
+ CHILD_REQUIRE(sig == SIGUSR1);
+ counter++;
+ if (counter == 2)
+ _exit(2);
+}
+
+/*
+ * Verify that, when continuing from a stop at system call entry and exit,
+ * a signal can be requested from both stops, and both will be delivered when
+ * the system call is complete.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_system_call_entry_and_exit);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_system_call_entry_and_exit, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE(signal(SIGUSR1, sigusr1_counting_handler) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ getpid();
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP and tracing system calls. */
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report a system call entry for getpid(). */
+ 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_SCE);
+
+ /* Continue the child process with a signal. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ /* The third wait() should report a system call exit for getpid(). */
+ 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_SCX);
+
+ /* Continue the child process with a signal. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ for (;;) {
+ /*
+ * The last wait() should report exit 2, i.e., a normal _exit
+ * from the signal handler. In the meantime, catch and proceed
+ * past any syscall stops.
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX));
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ } else {
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+ break;
+ }
+ }
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that even if the signal queue is full for a child process,
+ * a PT_CONTINUE with a signal will not result in loss of that signal.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_full_sigqueue);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_full_sigqueue, tc)
+{
+ pid_t fpid, wpid;
+ int status;
+ int max_pending_per_proc;
+ size_t len;
+ int i;
+
+ ATF_REQUIRE(signal(SIGUSR2, handler) != SIG_ERR);
+ ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ exit(1);
+ }
+
+ /* 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);
+
+ len = sizeof(max_pending_per_proc);
+ ATF_REQUIRE(sysctlbyname("kern.sigqueue.max_pending_per_proc",
+ &max_pending_per_proc, &len, NULL, 0) == 0);
+
+ /* Fill the signal queue. */
+ for (i = 0; i < max_pending_per_proc; ++i)
+ ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+ /* Continue with signal. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ for (;;) {
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ if (WIFSTOPPED(status)) {
+ ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ } else {
+ /*
+ * The last wait() should report normal _exit from the
+ * SIGUSR1 handler.
+ */
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+ break;
+ }
+ }
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that, after stopping due to a signal, that signal can be
+ * replaced with another signal.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_change_sig);
+ATF_TC_BODY(ptrace__PT_CONTINUE_change_sig, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ sleep(20);
+ exit(1);
+ }
+
+ /* 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_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* Send a signal without ptrace. */
+ ATF_REQUIRE(kill(fpid, SIGINT) == 0);
+
+ /* The second wait() should report a SIGINT was received. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGINT);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI);
+ ATF_REQUIRE(pl.pl_siginfo.si_signo == SIGINT);
+
+ /* Continue the child process with a different signal. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGTERM) == 0);
+
+ /*
+ * The last wait() should report having died due to the new
+ * signal, SIGTERM.
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGTERM);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a signal can be passed through to the child even when there
+ * was no true signal originally. Such cases arise when a SIGTRAP is
+ * invented for e.g, system call stops.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_sigtrap_system_call_entry);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_sigtrap_system_call_entry, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ getpid();
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP and tracing system calls. */
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report a system call entry for getpid(). */
+ 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_SCE);
+
+ /* Continue the child process with a SIGTRAP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGTRAP) == 0);
+
+ for (;;) {
+ /*
+ * The last wait() should report exit due to SIGTRAP. In the
+ * meantime, catch and proceed past any syscall stops.
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX));
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ } else {
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE(WTERMSIG(status) == SIGTRAP);
+ break;
+ }
+ }
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+
+}
+
+/*
+ * A mixed bag PT_CONTINUE with signal test.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_mix);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_mix, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE(signal(SIGUSR1, sigusr1_counting_handler) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ getpid();
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP and tracing system calls. */
+ ATF_REQUIRE(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0) == 0);
+
+ /* The second wait() should report a system call entry for getpid(). */
+ 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_SCE);
+
+ /* Continue with the first SIGUSR1. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ /* The next wait() should report a system call exit for getpid(). */
+ 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_SCX);
+
+ /* Send an ABRT without ptrace. */
+ ATF_REQUIRE(kill(fpid, SIGABRT) == 0);
+
+ /* Continue normally. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The next wait() should report the SIGABRT. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGABRT);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI);
+ ATF_REQUIRE(pl.pl_siginfo.si_signo == SIGABRT);
+
+ /* Continue, replacing the SIGABRT with another SIGUSR1. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ for (;;) {
+ /*
+ * The last wait() should report exit 2, i.e., a normal _exit
+ * from the signal handler. In the meantime, catch and proceed
+ * past any syscall stops.
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX));
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ } else {
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+ break;
+ }
+ }
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+
+}
+
+/*
+ * Verify a signal delivered by ptrace is noticed by kevent(2).
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_kqueue);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_kqueue, tc)
+{
+ pid_t fpid, wpid;
+ int status, kq, nevents;
+ struct kevent kev;
+
+ ATF_REQUIRE(signal(SIGUSR1, SIG_IGN) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ CHILD_REQUIRE((kq = kqueue()) > 0);
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, 0);
+ CHILD_REQUIRE(kevent(kq, &kev, 1, NULL, 0, NULL) == 0);
+
+ trace_me();
+
+ for (;;) {
+ nevents = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (nevents == -1 && errno == EINTR)
+ continue;
+ CHILD_REQUIRE(nevents > 0);
+ CHILD_REQUIRE(kev.filter == EVFILT_SIGNAL);
+ CHILD_REQUIRE(kev.ident == SIGUSR1);
+ break;
+ }
+
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue with the SIGUSR1. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ /*
+ * The last wait() should report normal exit with code 1.
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static sem_t sigusr1_sem;
+
+static void
+sigusr1_sempost_handler(int sig __unused)
+{
+
+ CHILD_REQUIRE(sem_post(&sigusr1_sem) == 0);
+}
+
+static void *
+signal_thread(void *arg)
+{
+ int err;
+ sigset_t sigmask;
+
+ pthread_barrier_t *pbarrier = (pthread_barrier_t*)arg;
+
+ /* Wait for this thread to receive a SIGUSR1. */
+ do {
+ err = sem_wait(&sigusr1_sem);
+ CHILD_REQUIRE(err == 0 || errno == EINTR);
+ } while (err != 0 && errno == EINTR);
+
+ /* Free our companion thread from the barrier. */
+ pthread_barrier_wait(pbarrier);
+
+ /*
+ * Swap ignore duties; the next SIGUSR1 should go to the
+ * other thread.
+ */
+ CHILD_REQUIRE(sigemptyset(&sigmask) == 0);
+ CHILD_REQUIRE(sigaddset(&sigmask, SIGUSR1) == 0);
+ CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+ /* Sync up threads after swapping signal masks. */
+ pthread_barrier_wait(pbarrier);
+
+ /* Wait until our companion has received its SIGUSR1. */
+ pthread_barrier_wait(pbarrier);
+
+ return (NULL);
+}
+
+/*
+ * Verify that if ptrace stops due to a signal but continues with
+ * a different signal that the new signal is routed to a thread
+ * that can accept it, and that that thread is awakened by the signal
+ * in a timely manner.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_thread_sigmask);
+ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_thread_sigmask, tc)
+{
+ pid_t fpid, wpid;
+ int status, err;
+ pthread_t t;
+ sigset_t sigmask;
+ pthread_barrier_t barrier;
+
+ ATF_REQUIRE(pthread_barrier_init(&barrier, NULL, 2) == 0);
+ ATF_REQUIRE(sem_init(&sigusr1_sem, 0, 0) == 0);
+ ATF_REQUIRE(signal(SIGUSR1, sigusr1_sempost_handler) != SIG_ERR);
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ CHILD_REQUIRE(pthread_create(&t, NULL, signal_thread, (void*)&barrier) == 0);
+
+ /* The other thread should receive the first SIGUSR1. */
+ CHILD_REQUIRE(sigemptyset(&sigmask) == 0);
+ CHILD_REQUIRE(sigaddset(&sigmask, SIGUSR1) == 0);
+ CHILD_REQUIRE(pthread_sigmask(SIG_BLOCK, &sigmask, NULL) == 0);
+
+ trace_me();
+
+ /* Wait until other thread has received its SIGUSR1. */
+ pthread_barrier_wait(&barrier);
+
+ /*
+ * Swap ignore duties; the next SIGUSR1 should go to this
+ * thread.
+ */
+ CHILD_REQUIRE(pthread_sigmask(SIG_UNBLOCK, &sigmask, NULL) == 0);
+
+ /* Sync up threads after swapping signal masks. */
+ pthread_barrier_wait(&barrier);
+
+ /*
+ * Sync up with test code; we're ready for the next SIGUSR1
+ * now.
+ */
+ raise(SIGSTOP);
+
+ /* Wait for this thread to receive a SIGUSR1. */
+ do {
+ err = sem_wait(&sigusr1_sem);
+ CHILD_REQUIRE(err == 0 || errno == EINTR);
+ } while (err != 0 && errno == EINTR);
+
+ /* Free the other thread from the barrier. */
+ pthread_barrier_wait(&barrier);
+
+ CHILD_REQUIRE(pthread_join(t, NULL) == 0);
+
+ exit(1);
+ }
+
+ /* 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);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /*
+ * Send a signal without ptrace that either thread will accept (USR2,
+ * in this case).
+ */
+ ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+ /* The second wait() should report a SIGUSR2 was received. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+
+ /* Continue the child, changing the signal to USR1. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ /* The next 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);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ ATF_REQUIRE(kill(fpid, SIGUSR2) == 0);
+
+ /* The next wait() should report a SIGUSR2 was received. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGUSR2);
+
+ /* Continue the child, changing the signal to USR1. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1) == 0);
+
+ /* The last wait() should report normal exit with code 1. */
+ wpid = waitpid(fpid, &status, 0);
+ 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_TP_ADD_TCS(tp)
{
@@ -1700,6 +2805,21 @@ ATF_TP_ADD_TCS(tp)
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);
+ ATF_TP_ADD_TC(tp, ptrace__PT_KILL_breakpoint);
+ ATF_TP_ADD_TC(tp, ptrace__PT_KILL_system_call);
+ ATF_TP_ADD_TC(tp, ptrace__PT_KILL_threads);
+ ATF_TP_ADD_TC(tp, ptrace__PT_KILL_competing_signal);
+ ATF_TP_ADD_TC(tp, ptrace__PT_KILL_competing_stop);
+ ATF_TP_ADD_TC(tp, ptrace__PT_KILL_with_signal_full_sigqueue);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_system_call_entry);
+ ATF_TP_ADD_TC(tp,
+ ptrace__PT_CONTINUE_with_signal_system_call_entry_and_exit);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_full_sigqueue);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_change_sig);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_sigtrap_system_call_entry);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_mix);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_kqueue);
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_thread_sigmask);
return (atf_no_error());
}