Index: sys/kern/kern_fork.c =================================================================== --- sys/kern/kern_fork.c +++ sys/kern/kern_fork.c @@ -1067,7 +1067,7 @@ 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. @@ -1075,9 +1075,10 @@ 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); } Index: sys/kern/kern_sig.c =================================================================== --- sys/kern/kern_sig.c +++ sys/kern/kern_sig.c @@ -2501,7 +2501,12 @@ 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); @@ -2531,6 +2536,7 @@ } } PROC_SUNLOCK(p); + /* XXX: Clear TDB_XSIG in case P_TRACED was never set? */ return (td->td_xsig); } Index: sys/kern/kern_thr.c =================================================================== --- sys/kern/kern_thr.c +++ sys/kern/kern_thr.c @@ -253,6 +253,8 @@ 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 @@ 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); } + 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); + tidhash_remove(td); + PROC_LOCK(p); + p->p_pendingexits--; + /* - * Ignore attempts to shut down last thread in the proc. This - * will actually call _exit(2) in the usermode trampoline when - * it returns. + * 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. */ - PROC_UNLOCK(p); - rw_wunlock(&tidhash_lock); - return (0); + 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 Index: sys/kern/kern_thread.c =================================================================== --- sys/kern/kern_thread.c +++ sys/kern/kern_thread.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -942,7 +943,6 @@ */ if ((p->p_flag & P_SINGLE_EXIT) && (p->p_singlethread != td)) { PROC_UNLOCK(p); - tidhash_remove(td); /* * Allow Linux emulation layer to do some work @@ -950,13 +950,8 @@ */ if (__predict_false(p->p_sysent->sv_thread_detach != NULL)) (p->p_sysent->sv_thread_detach)(td); - - PROC_LOCK(p); - tdsigcleanup(td); - umtx_thread_exit(td); - PROC_SLOCK(p); - thread_stopped(p); - thread_exit(); + kern_thr_exit(td); + panic("stopped thread did not exit"); } PROC_SLOCK(p); Index: sys/kern/sys_process.c =================================================================== --- sys/kern/sys_process.c +++ sys/kern/sys_process.c @@ -710,6 +710,7 @@ 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 @@ 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 @@ } 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); Index: sys/sys/proc.h =================================================================== --- sys/sys/proc.h +++ sys/sys/proc.h @@ -412,6 +412,8 @@ #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 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 @@ #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 */ Index: sys/sys/ptrace.h =================================================================== --- sys/sys/ptrace.h +++ sys/sys/ptrace.h @@ -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 @@ #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 */ Index: tests/sys/kern/ptrace_test.c =================================================================== --- tests/sys/kern/ptrace_test.c +++ tests/sys/kern/ptrace_test.c @@ -1094,6 +1094,16 @@ 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_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_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_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()); }