Index: lib/libc/sys/ptrace.2 =================================================================== RCS file: /cvsroot/src/lib/libc/sys/ptrace.2,v retrieving revision 1.58 diff -u -r1.58 ptrace.2 --- lib/libc/sys/ptrace.2 27 Jan 2017 12:52:39 -0000 1.58 +++ lib/libc/sys/ptrace.2 10 Feb 2017 23:29:57 -0000 @@ -355,6 +355,7 @@ .Bl -tag -width 30n -offset indent -compact .It Dv PL_EVENT_NONE .It Dv PL_EVENT_SIGNAL +.It Dv PL_EVENT_SUSPENDED .El .Pp The @@ -514,6 +515,34 @@ .Fa data argument should be set to .Li sizeof(struct ptrace_siginfo) . +.It Dv PT_RESUME +Unlock a specified thread, +change its state from suspended to continued. +The +.Fa addr +argument is unused. +The +.Fa data +argument specifies the LWP ID. +.Pp +This call is equivalent to +.Xr _lwp_continue 2 +called by a traced process. +This call does not change the general process state from stopped to continued. +.It Dv PT_SUSPEND +Lock a specified thread, +change its state from continued to suspended. +The +.Fa addr +argument is unused. +The +.Fa data +argument specifies the requested LWP ID. +.Pp +This call is equivalent to +.Xr _lwp_continue 2 +called by a traced process. +This call does not change the general process state from continued to stopped. .El .Pp Additionally, the following requests exist but are @@ -667,6 +696,8 @@ .Dv PT_ATTACH ) specified a process that wasn't stopped. .El +.It Bq Er EDEADLK +An attempt to unstop a process with locked threads. .It Bq Er EINVAL .Bl -bullet -compact .It @@ -736,3 +767,10 @@ .Dv PTRACE_VFORK is currently unimplemented and it will return .Er ENOTSUP . +.Pp +.Dv PT_SET_SIGINFO , +.Dv PT_RESUME +and +.Dv PT_SUSPEND +can change the image of process returned by +.Dv PT_LWPINFO . Index: sys/kern/sys_ptrace_common.c =================================================================== RCS file: /cvsroot/src/sys/kern/sys_ptrace_common.c,v retrieving revision 1.12 diff -u -r1.12 sys_ptrace_common.c --- sys/kern/sys_ptrace_common.c 26 Jan 2017 03:54:01 -0000 1.12 +++ sys/kern/sys_ptrace_common.c 10 Feb 2017 23:30:21 -0000 @@ -234,6 +234,8 @@ case PT_SYSCALL: case PT_SYSCALLEMU: case PT_DUMPCORE: + case PT_RESUME: + case PT_SUSPEND: result = KAUTH_RESULT_ALLOW; break; @@ -453,6 +455,8 @@ case PT_GET_PROCESS_STATE: case PT_SET_SIGINFO: case PT_GET_SIGINFO: + case PT_RESUME: + case PT_SUSPEND: /* * You can't do what you want to the process if: * (1) It's not being traced at all, @@ -751,6 +755,34 @@ break; } + /* Prevent process deadlock */ + if (resume_all) { +#ifdef PT_STEP + if (req == PT_STEP) { + if (lt->l_flag & LW_WSUSPEND) { + error = EDEADLK; + break; + } + } else +#endif + { + error = EDEADLK; + LIST_FOREACH(lt2, &t->p_lwps, l_sibling) { + if ((lt2->l_flag & LW_WSUSPEND) == 0) { + error = 0; + break; + } + } + if (error != 0) + break; + } + } else { + if (lt->l_flag & LW_WSUSPEND) { + error = EDEADLK; + break; + } + } + /* If the address parameter is not (int *)1, set the pc. */ if ((int *)addr != (int *)1) { error = process_set_pc(lt, addr); @@ -964,15 +996,18 @@ if (lt) { lwp_addref(lt); pl.pl_lwpid = lt->l_lid; + + if (lt->l_flag & LW_WSUSPEND) + pl.pl_event = PL_EVENT_SUSPENDED; /* * If we match the lwp, or it was sent to every lwp, * we set PL_EVENT_SIGNAL. * XXX: ps_lwp == 0 means everyone and noone, so * check ps_signo too. */ - if (lt->l_lid == t->p_sigctx.ps_lwp - || (t->p_sigctx.ps_lwp == 0 && - t->p_sigctx.ps_info._signo)) + else if (lt->l_lid == t->p_sigctx.ps_lwp + || (t->p_sigctx.ps_lwp == 0 && + t->p_sigctx.ps_info._signo)) pl.pl_event = PL_EVENT_SIGNAL; } mutex_exit(t->p_lock); @@ -1039,6 +1074,37 @@ break; + case PT_RESUME: + write = 1; + + case PT_SUSPEND: + /* write = 0 done above. */ + + tmp = data; + if (tmp != 0 && t->p_nlwps > 1) { + lwp_delref(lt); + mutex_enter(t->p_lock); + lt = lwp_find(t, tmp); + if (lt == NULL) { + mutex_exit(t->p_lock); + error = ESRCH; + break; + } + lwp_addref(lt); + mutex_exit(t->p_lock); + } + if (!process_validregs(lt)) { + error = EINVAL; + } else { + lwp_lock(lt); + if (write == 0) + lt->l_flag |= LW_WSUSPEND; + else + lt->l_flag &= ~LW_WSUSPEND; + lwp_unlock(lt); + } + break; + #ifdef PT_SETREGS case PT_SETREGS: write = 1; Index: sys/sys/ptrace.h =================================================================== RCS file: /cvsroot/src/sys/sys/ptrace.h,v retrieving revision 1.55 diff -u -r1.55 ptrace.h --- sys/sys/ptrace.h 16 Jan 2017 21:35:59 -0000 1.55 +++ sys/sys/ptrace.h 10 Feb 2017 23:30:22 -0000 @@ -55,6 +55,8 @@ #define PT_GET_PROCESS_STATE 18 /* get process state, defined below */ #define PT_SET_SIGINFO 19 /* set signal state, defined below */ #define PT_GET_SIGINFO 20 /* get signal state, defined below */ +#define PT_RESUME 21 /* resume specified thread */ +#define PT_SUSPEND 22 /* suspend specified thread */ #define PT_FIRSTMACH 32 /* for machine-specific requests */ #include /* machine-specific requests, if any */ @@ -131,8 +133,9 @@ /* Add fields at the end */ }; -#define PL_EVENT_NONE 0 -#define PL_EVENT_SIGNAL 1 +#define PL_EVENT_NONE 0 +#define PL_EVENT_SIGNAL 1 +#define PL_EVENT_SUSPENDED 2 /* * Hardware Watchpoints Index: tests/kernel/t_ptrace_wait.c =================================================================== RCS file: /cvsroot/src/tests/kernel/t_ptrace_wait.c,v retrieving revision 1.69 diff -u -r1.69 t_ptrace_wait.c --- tests/kernel/t_ptrace_wait.c 27 Jan 2017 16:43:07 -0000 1.69 +++ tests/kernel/t_ptrace_wait.c 10 Feb 2017 23:30:22 -0000 @@ -6583,6 +6583,338 @@ TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0)); } +static void +lwp_main_stop(void *arg) +{ + the_lwp_id = _lwp_self(); + + raise(SIGTRAP); + + _lwp_exit(); +} + +ATF_TC(suspend1); +ATF_TC_HEAD(suspend1, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify that a thread can be suspended by a debugger and later " + "resumed by a tracee"); +} + +ATF_TC_BODY(suspend1, tc) +{ + const int exitval = 5; + const int sigval = SIGSTOP; + pid_t child, wpid; +#if defined(TWAIT_HAVE_STATUS) + int status; +#endif + ucontext_t uc; + lwpid_t lid; + static const size_t ssize = 16*1024; + void *stack; + struct ptrace_lwpinfo pl; + struct ptrace_siginfo psi; + volatile int go = 0; + + printf("Before forking process PID=%d\n", getpid()); + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + printf("Before calling PT_TRACE_ME from child %d\n", getpid()); + FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1); + + printf("Before raising %s from child\n", strsignal(sigval)); + FORKEE_ASSERT(raise(sigval) == 0); + + printf("Before allocating memory for stack in child\n"); + FORKEE_ASSERT((stack = malloc(ssize)) != NULL); + + printf("Before making context for new lwp in child\n"); + _lwp_makecontext(&uc, lwp_main_stop, NULL, NULL, stack, ssize); + + printf("Before creating new in child\n"); + FORKEE_ASSERT(_lwp_create(&uc, 0, &lid) == 0); + + while (go == 0) + continue; + + raise(SIGINT); + + FORKEE_ASSERT(_lwp_continue(lid) == 0); + + printf("Before waiting for lwp %d to exit\n", lid); + FORKEE_ASSERT(_lwp_wait(lid, NULL) == 0); + + printf("Before verifying that reported %d and running lid %d " + "are the same\n", lid, the_lwp_id); + FORKEE_ASSERT_EQ(lid, the_lwp_id); + + printf("Before exiting of the child process\n"); + _exit(exitval); + } + printf("Parent process PID=%d, child's PID=%d\n", getpid(), child); + + printf("Before calling %s() for the child\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, sigval); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected stopped " + "SIGTRAP\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, SIGTRAP); + + printf("Before reading siginfo and lwpid_t\n"); + ATF_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1); + + printf("Before suspending LWP %d\n", psi.psi_lwpid); + ATF_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1); + + printf("Write new go to tracee (PID=%d) from tracer (PID=%d)\n", + child, getpid()); + ATF_REQUIRE(ptrace(PT_WRITE_D, child, __UNVOLATILE(&go), 1) != -1); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected stopped " + "SIGINT\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, SIGINT); + + pl.pl_lwpid = 0; + + ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1); + while (pl.pl_lwpid != 0) { + + ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1); + switch (pl.pl_lwpid) { + case 1: + ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SIGNAL); + break; + case 2: + ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SUSPENDED); + break; + } + } + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected exited\n", + TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_exited(status, exitval); + + printf("Before calling %s() for the child - expected no process\n", + TWAIT_FNAME); + TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0)); +} + +ATF_TC(suspend2); +ATF_TC_HEAD(suspend2, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify that the while the only thread within a process is " + "suspended it the whole process cannot be unstopped"); +} + +ATF_TC_BODY(suspend2, tc) +{ + const int exitval = 5; + const int sigval = SIGSTOP; + pid_t child, wpid; +#if defined(TWAIT_HAVE_STATUS) + int status; +#endif + struct ptrace_siginfo psi; + + printf("Before forking process PID=%d\n", getpid()); + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + printf("Before calling PT_TRACE_ME from child %d\n", getpid()); + FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1); + + printf("Before raising %s from child\n", strsignal(sigval)); + FORKEE_ASSERT(raise(sigval) == 0); + + printf("Before exiting of the child process\n"); + _exit(exitval); + } + printf("Parent process PID=%d, child's PID=%d\n", getpid(), child); + + printf("Before calling %s() for the child\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, sigval); + + printf("Before reading siginfo and lwpid_t\n"); + ATF_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1); + + printf("Before suspending LWP %d\n", psi.psi_lwpid); + ATF_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE_ERRNO(EDEADLK, + ptrace(PT_CONTINUE, child, (void *)1, 0) == -1); + + printf("Before resuming LWP %d\n", psi.psi_lwpid); + ATF_REQUIRE(ptrace(PT_RESUME, child, NULL, psi.psi_lwpid) != -1); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected exited\n", + TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_exited(status, exitval); + + printf("Before calling %s() for the child - expected no process\n", + TWAIT_FNAME); + TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0)); +} + +ATF_TC(resume1); +ATF_TC_HEAD(resume1, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify that a thread can be suspended by a debugger and later " + "resumed by the debugger"); +} + +ATF_TC_BODY(resume1, tc) +{ + struct msg_fds fds; + const int exitval = 5; + const int sigval = SIGSTOP; + pid_t child, wpid; + uint8_t msg = 0xde; /* dummy message for IPC based on pipe(2) */ +#if defined(TWAIT_HAVE_STATUS) + int status; +#endif + ucontext_t uc; + lwpid_t lid; + static const size_t ssize = 16*1024; + void *stack; + struct ptrace_lwpinfo pl; + struct ptrace_siginfo psi; + + ATF_REQUIRE(msg_open(&fds) == 0); + + printf("Before forking process PID=%d\n", getpid()); + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + printf("Before calling PT_TRACE_ME from child %d\n", getpid()); + FORKEE_ASSERT(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1); + + printf("Before raising %s from child\n", strsignal(sigval)); + FORKEE_ASSERT(raise(sigval) == 0); + + printf("Before allocating memory for stack in child\n"); + FORKEE_ASSERT((stack = malloc(ssize)) != NULL); + + printf("Before making context for new lwp in child\n"); + _lwp_makecontext(&uc, lwp_main_stop, NULL, NULL, stack, ssize); + + printf("Before creating new in child\n"); + FORKEE_ASSERT(_lwp_create(&uc, 0, &lid) == 0); + + CHILD_TO_PARENT("Message", fds, msg); + + raise(SIGINT); + + printf("Before waiting for lwp %d to exit\n", lid); + FORKEE_ASSERT(_lwp_wait(lid, NULL) == 0); + + printf("Before verifying that reported %d and running lid %d " + "are the same\n", lid, the_lwp_id); + FORKEE_ASSERT_EQ(lid, the_lwp_id); + + printf("Before exiting of the child process\n"); + _exit(exitval); + } + printf("Parent process PID=%d, child's PID=%d\n", getpid(), child); + + printf("Before calling %s() for the child\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, sigval); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected stopped " + "SIGTRAP\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, SIGTRAP); + + printf("Before reading siginfo and lwpid_t\n"); + ATF_REQUIRE(ptrace(PT_GET_SIGINFO, child, &psi, sizeof(psi)) != -1); + + printf("Before suspending LWP %d\n", psi.psi_lwpid); + ATF_REQUIRE(ptrace(PT_SUSPEND, child, NULL, psi.psi_lwpid) != -1); + + PARENT_FROM_CHILD("Message", fds, msg); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected stopped " + "SIGINT\n", TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_stopped(status, SIGINT); + + pl.pl_lwpid = 0; + + ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1); + while (pl.pl_lwpid != 0) { + ATF_REQUIRE(ptrace(PT_LWPINFO, child, &pl, sizeof(pl)) != -1); + switch (pl.pl_lwpid) { + case 1: + ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SIGNAL); + break; + case 2: + ATF_REQUIRE_EQ(pl.pl_event, PL_EVENT_SUSPENDED); + break; + } + } + + printf("Before resuming LWP %d\n", psi.psi_lwpid); + ATF_REQUIRE(ptrace(PT_RESUME, child, NULL, psi.psi_lwpid) != -1); + + printf("Before resuming the child process where it left off and " + "without signal to be sent\n"); + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (void *)1, 0) != -1); + + printf("Before calling %s() for the child - expected exited\n", + TWAIT_FNAME); + TWAIT_REQUIRE_SUCCESS(wpid = TWAIT_GENERIC(child, &status, 0), child); + + validate_status_exited(status, exitval); + + printf("Before calling %s() for the child - expected no process\n", + TWAIT_FNAME); + TWAIT_REQUIRE_FAILURE(ECHILD, wpid = TWAIT_GENERIC(child, &status, 0)); + + msg_close(&fds); +} + ATF_TP_ADD_TCS(tp) { setvbuf(stdout, NULL, _IONBF, 0); @@ -6696,5 +7028,10 @@ ATF_TP_ADD_TC(tp, signal9); ATF_TP_ADD_TC(tp, signal10); + ATF_TP_ADD_TC(tp, suspend1); + ATF_TP_ADD_TC(tp, suspend2); + + ATF_TP_ADD_TC(tp, resume1); + return atf_no_error(); }