Index: sys/kern/kern_fork.c =================================================================== --- sys/kern/kern_fork.c +++ sys/kern/kern_fork.c @@ -1081,7 +1081,7 @@ 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 @@ _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); } Index: sys/kern/kern_sig.c =================================================================== --- sys/kern/kern_sig.c +++ sys/kern/kern_sig.c @@ -360,6 +360,10 @@ 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,8 +402,9 @@ ksi->ksi_sigq = sq; } - if ((si->ksi_flags & KSI_TRAP) != 0 || - (si->ksi_flags & KSI_SIGQ) == 0) { + if ((si->ksi_flags & KSI_PTRACE) == 0 && + ((si->ksi_flags & KSI_TRAP) != 0 || + (si->ksi_flags & KSI_SIGQ) == 0)) { if (ret != 0) SIGADDSET(sq->sq_kill, signo); ret = 0; @@ -2500,69 +2505,111 @@ 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)) { - /* - * Ignore ptrace stops except for thread exit - * events when the process exits. - */ - td->td_dbgflags &= ~TDB_XSIG; - PROC_SUNLOCK(p); - return (sig); - } + if (si && P_KILLED(p)) { /* - * 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. + * 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. */ - 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); - } + td->td_xsig = SIGKILL; + } else 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->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); + } + + /* + * 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 ((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) + thread_suspend_switch(td, p); + if (p->p_xthread == td) + p->p_xthread = NULL; + if (!(p->p_flag & P_TRACED)) break; - goto stopme; + if (td->td_dbgflags & TDB_SUSPEND) { + if (p->p_flag & P_SINGLE_EXIT) + break; + goto stopme; + } } + PROC_SUNLOCK(p); } - PROC_SUNLOCK(p); + + if (si && 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) { + /* + * 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. + */ + 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); + } + return (td->td_xsig); } @@ -2720,7 +2767,7 @@ 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 +2830,18 @@ } 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; } Index: sys/kern/kern_thr.c =================================================================== --- sys/kern/kern_thr.c +++ sys/kern/kern_thr.c @@ -356,7 +356,7 @@ 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); Index: sys/kern/subr_syscall.c =================================================================== --- sys/kern/subr_syscall.c +++ sys/kern/subr_syscall.c @@ -88,7 +88,7 @@ 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 @@ 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 @@ 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); } Index: sys/kern/sys_process.c =================================================================== --- sys/kern/sys_process.c +++ sys/kern/sys_process.c @@ -1125,6 +1125,16 @@ td2->td_dbgflags &= ~TDB_XSIG; td2->td_xsig = data; + /* + * P_WKILLED is insurance that a PT_KILL 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; Index: sys/sys/signalvar.h =================================================================== --- sys/sys/signalvar.h +++ sys/sys/signalvar.h @@ -237,7 +237,8 @@ #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) @@ -370,7 +371,7 @@ 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); Index: tests/sys/kern/Makefile =================================================================== --- tests/sys/kern/Makefile +++ tests/sys/kern/Makefile @@ -8,6 +8,7 @@ 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 Index: tests/sys/kern/ptrace_test.c =================================================================== --- tests/sys/kern/ptrace_test.c +++ tests/sys/kern/ptrace_test.c @@ -28,6 +28,9 @@ __FBSDID("$FreeBSD$"); #include +#include +#include +#include #include #include #include @@ -35,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -1673,6 +1677,905 @@ 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); +} + +static void +sigusr1_handler(int sig) +{ + + CHILD_REQUIRE(sig == SIGUSR1); + _exit(2); +} + +/* + * 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_KILL will kill the process. + */ +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(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, 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 +2603,19 @@ 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_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()); }