summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2018-01-24 21:48:39 +0000
committerjhb <jhb@FreeBSD.org>2018-01-24 21:48:39 +0000
commitcdcaa31bd43a7beacec9fe4541a0c73d80f10ccb (patch)
tree7247b21ea3de20f640097e9d318b4f935bfa6f6b /tests
parent3d8890c2b7f6938fe084da4969bfd8c6117574ce (diff)
downloadFreeBSD-src-cdcaa31bd43a7beacec9fe4541a0c73d80f10ccb.zip
FreeBSD-src-cdcaa31bd43a7beacec9fe4541a0c73d80f10ccb.tar.gz
MFC 325028,328344: Discard the correct thread event reported for a ptrace stop.
325028: Discard the correct thread event reported for a ptrace stop. When multiple threads wish to report a tracing event to a debugger, both threads call ptracestop() and one thread will win the race to be the reporting thread (p->p_xthread). The debugger uses PT_LWPINFO with the process ID to determine which thread / LWP is reporting an event and the details of that event. This event is cleared as a side effect of the subsequent ptrace event that resumed the process (PT_CONTINUE, PT_STEP, etc.). However, ptrace() was clearing the event identified by the LWP ID passed to the resume request even if that wasn't the 'p_xthread'. This could result in clearing an event that had not yet been observed by the debugger and leaving the existing event for 'p_thread' pending so that it was reported a second time. Specifically, if the debugger stopped due to a software breakpoint in one thread, but then switched to another thread that was used to resume (e.g. if the user switched to a different thread and issued a step), the resume request (PT_STEP) cleared a pending event (if any) for the thread being stepped. However, the process immediately stopped and the first thread reported it's breakpoint event a second time. The debugger decremented the PC for "both" breakpoint events which resulted in the PC now pointing into the middle of an instruction (on x86) and a SIGILL fault when the process was resumed a second time. To fix, always clear the pending event for 'p_xthread' when resuming a process. ptrace() still honors the requested LWP ID when enabling single-stepping (PT_STEP) or setting a different PC (PT_CONTINUE). 328344: Mark the unused argument to continue_thread() as such. clang in HEAD and 11 does not warn about this, but clang in 10 does.
Diffstat (limited to 'tests')
-rw-r--r--tests/sys/kern/ptrace_test.c171
1 files changed, 171 insertions, 0 deletions
diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c
index d7b3222..5c1b66d 100644
--- a/tests/sys/kern/ptrace_test.c
+++ b/tests/sys/kern/ptrace_test.c
@@ -3468,6 +3468,174 @@ ATF_TC_BODY(ptrace__PT_STEP_with_signal, tc)
ATF_REQUIRE(errno == ECHILD);
}
+#if defined(__amd64__) || defined(__i386__)
+/*
+ * Only x86 both define breakpoint() and have a PC after breakpoint so
+ * that restarting doesn't retrigger the breakpoint.
+ */
+static void *
+continue_thread(void *arg __unused)
+{
+ breakpoint();
+ return (NULL);
+}
+
+static __dead2 void
+continue_thread_main(void)
+{
+ pthread_t threads[2];
+
+ CHILD_REQUIRE(pthread_create(&threads[0], NULL, continue_thread,
+ NULL) == 0);
+ CHILD_REQUIRE(pthread_create(&threads[1], NULL, continue_thread,
+ NULL) == 0);
+ CHILD_REQUIRE(pthread_join(threads[0], NULL) == 0);
+ CHILD_REQUIRE(pthread_join(threads[1], NULL) == 0);
+ exit(1);
+}
+
+/*
+ * Ensure that PT_CONTINUE clears the status of the thread that
+ * triggered the stop even if a different thread's LWP was passed to
+ * PT_CONTINUE.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_different_thread);
+ATF_TC_BODY(ptrace__PT_CONTINUE_different_thread, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ lwpid_t lwps[2];
+ bool hit_break[2];
+ int i, j, status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ continue_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);
+
+ 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);
+
+ /* One of the new threads should report it'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));
+ lwps[0] = pl.pl_lwpid;
+
+ /*
+ * Suspend this thread to ensure both threads are alive before
+ * hitting the breakpoint.
+ */
+ ATF_REQUIRE(ptrace(PT_SUSPEND, lwps[0], NULL, 0) != -1);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* Second thread should report it'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;
+
+ /* Resume both threads waiting for breakpoint events. */
+ hit_break[0] = hit_break[1] = false;
+ ATF_REQUIRE(ptrace(PT_RESUME, lwps[0], NULL, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* One thread should report a breakpoint. */
+ 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_SI) != 0);
+ ATF_REQUIRE(pl.pl_siginfo.si_signo == SIGTRAP &&
+ pl.pl_siginfo.si_code == TRAP_BRKPT);
+ if (pl.pl_lwpid == lwps[0])
+ i = 0;
+ else
+ i = 1;
+ hit_break[i] = true;
+
+ /*
+ * Resume both threads but pass the other thread's LWPID to
+ * PT_CONTINUE.
+ */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, lwps[i ^ 1], (caddr_t)1, 0) == 0);
+
+ /*
+ * Will now get two thread exit events and one more breakpoint
+ * event.
+ */
+ for (j = 0; j < 3; j++) {
+ 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);
+
+ if (pl.pl_lwpid == lwps[0])
+ i = 0;
+ else
+ i = 1;
+
+ ATF_REQUIRE_MSG(lwps[i] != 0, "event for exited thread");
+ if (pl.pl_flags & PL_FLAG_EXITED) {
+ ATF_REQUIRE_MSG(hit_break[i],
+ "exited thread did not report breakpoint");
+ lwps[i] = 0;
+ } else {
+ ATF_REQUIRE((pl.pl_flags & PL_FLAG_SI) != 0);
+ ATF_REQUIRE(pl.pl_siginfo.si_signo == SIGTRAP &&
+ pl.pl_siginfo.si_code == TRAP_BRKPT);
+ ATF_REQUIRE_MSG(!hit_break[i],
+ "double breakpoint event");
+ hit_break[i] = true;
+ }
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ }
+
+ /* Both threads should have exited. */
+ ATF_REQUIRE(lwps[0] == 0);
+ ATF_REQUIRE(lwps[1] == 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);
+}
+#endif
+
ATF_TP_ADD_TCS(tp)
{
@@ -3520,6 +3688,9 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, ptrace__event_mask_sigkill_discard);
ATF_TP_ADD_TC(tp, ptrace__PT_ATTACH_with_SBDRY_thread);
ATF_TP_ADD_TC(tp, ptrace__PT_STEP_with_signal);
+#if defined(__amd64__) || defined(__i386__)
+ ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_different_thread);
+#endif
return (atf_no_error());
}
OpenPOWER on IntegriCloud