mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
tests/sys/kern: stress FP registers across sigreturn on powerpc64
This is the program that found the races fixed in1f1043d550. One thread keeps a pattern in f14-f31 across a signal, while helpers pinned to the same CPU keep different patterns in the real registers and get woken up from another CPU to preempt it at random points. If the kernel ever hands it somebody else's registers, the pattern doesn't match. The register loads, the thr_kill() and the handshake with the helper are in assembly so that nothing touches the FP registers between sigreturn and the check. It runs for ten seconds and is skipped with fewer than two CPUs. Requested by: adrian Approved by: jhibbits MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D60411 (cherry picked from commit35a226dded) (cherry picked from commit8d36589470)
This commit is contained in:
1 parent
9bc2baac4f
commit
b1b2b22c8d
3 files changed
+414
No files matched your search
@@ -36,6 +36,12 @@ ATF_TESTS_C+= reaper
|
||||
ATF_TESTS_C+= resolve_beneath_test
|
||||
ATF_TESTS_C+= sched_affinity
|
||||
ATF_TESTS_C+= sigaltstack
|
||||
.if ${MACHINE_ARCH:Mpowerpc64*} != ""
|
||||
ATF_TESTS_C+= sigreturn_fpregs
|
||||
SRCS.sigreturn_fpregs= sigreturn_fpregs.c sigreturn_fpregs_ppc64.S
|
||||
LIBADD.sigreturn_fpregs+= pthread
|
||||
TEST_METADATA.sigreturn_fpregs+= is_exclusive="true"
|
||||
.endif
|
||||
ATF_TESTS_C+= sigwait
|
||||
ATF_TESTS_C+= socket_accf
|
||||
ATF_TESTS_C+= socket_msg_trunc
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2026 Piotr Kubaj <pkubaj@FreeBSD.org>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Stress test for the lazy reload of the floating point registers after
|
||||
* sigreturn(2) on powerpc64.
|
||||
*
|
||||
* sigreturn turns the FPU off for the thread; the next FP instruction takes
|
||||
* the FP unavailable trap, which reloads the registers from the PCB. The
|
||||
* checker loads f14-f31 with a pattern, sends itself a signal and stores the
|
||||
* registers again, so that store takes the trap. Between the two it pings a
|
||||
* helper thread pinned to its own CPU, so the hardware registers hold the
|
||||
* helper's values when the reload runs; more helpers on the same CPU are
|
||||
* woken from another CPU so that ULE preempts the checker at random points
|
||||
* of the sequence (only a remote wakeup preempts immediately). Any mismatch
|
||||
* means the kernel handed the checker another thread's registers. Before
|
||||
* 1f1043d550ea ("powerpc: keep FP, VMX and VSX ownership changes atomic
|
||||
* with preemption") a POWER9 saw about one mismatch per 3000 round trips.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/cpuset.h>
|
||||
#include <sys/thr.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atf-c.h>
|
||||
|
||||
/* sigreturn_fpregs_ppc64.S */
|
||||
void fp_roundtrip(const double *in, double *out, long tid, long sig,
|
||||
long wfd, long rfd);
|
||||
void fp_load_all(const double *pat);
|
||||
|
||||
#define DURATION 10 /* seconds */
|
||||
#define NPOLL 4 /* 3 woken by the waker, #3 woken by the checker */
|
||||
#define NREGS 18 /* f14-f31 */
|
||||
|
||||
static double pollpat[NPOLL][32];
|
||||
static int pin_in[NPOLL][2], pin_out[NPOLL][2];
|
||||
static int checker_cpu, waker_cpu;
|
||||
|
||||
static void
|
||||
xfer(int fd, int wr, char *c)
|
||||
{
|
||||
ssize_t n;
|
||||
|
||||
do
|
||||
n = wr ? write(fd, c, 1) : read(fd, c, 1);
|
||||
while (n < 0 && errno == EINTR);
|
||||
if (n != 1)
|
||||
err(1, wr ? "write" : "read");
|
||||
}
|
||||
|
||||
static void
|
||||
pin_thread(int cpu)
|
||||
{
|
||||
cpuset_t cs;
|
||||
|
||||
CPU_ZERO(&cs);
|
||||
CPU_SET(cpu, &cs);
|
||||
if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cs),
|
||||
&cs) != 0)
|
||||
err(1, "cpuset_setaffinity(%d)", cpu);
|
||||
}
|
||||
|
||||
static void
|
||||
handler(int sig __unused)
|
||||
{
|
||||
}
|
||||
|
||||
static void *
|
||||
polluter(void *arg)
|
||||
{
|
||||
int i = (int)(intptr_t)arg;
|
||||
char c;
|
||||
|
||||
pin_thread(checker_cpu);
|
||||
if (i == NPOLL - 1) /* the checker's helper: distinctive FPSCR */
|
||||
__asm__ volatile("mtfsb1 31");
|
||||
for (;;) {
|
||||
xfer(pin_in[i][0], 0, &c);
|
||||
fp_load_all(pollpat[i]);
|
||||
xfer(pin_out[i][1], 1, &c);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static void *
|
||||
waker(void *arg __unused)
|
||||
{
|
||||
uint64_t until;
|
||||
char c = 'x';
|
||||
int i;
|
||||
|
||||
pin_thread(waker_cpu);
|
||||
for (;;) {
|
||||
for (i = 0; i < NPOLL - 1; i++) {
|
||||
/*
|
||||
* Random 0-20 us delay so the preemption samples the
|
||||
* checker's loop at uniformly random phases.
|
||||
*/
|
||||
until = __builtin_ppc_get_timebase() + random() % 10240;
|
||||
while (__builtin_ppc_get_timebase() < until)
|
||||
;
|
||||
xfer(pin_in[i][1], 1, &c);
|
||||
xfer(pin_out[i][0], 0, &c);
|
||||
}
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Pick two distinct CPUs this process may run on. */
|
||||
static int
|
||||
pick_cpus(void)
|
||||
{
|
||||
cpuset_t cs;
|
||||
int i, n;
|
||||
|
||||
CPU_ZERO(&cs);
|
||||
ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID,
|
||||
-1, sizeof(cs), &cs));
|
||||
n = 0;
|
||||
for (i = 0; i < CPU_SETSIZE; i++) {
|
||||
if (!CPU_ISSET(i, &cs))
|
||||
continue;
|
||||
if (n == 0)
|
||||
checker_cpu = i;
|
||||
else if (n == 1)
|
||||
waker_cpu = i;
|
||||
n++;
|
||||
}
|
||||
return (n);
|
||||
}
|
||||
|
||||
ATF_TC(sigreturn_fpregs);
|
||||
ATF_TC_HEAD(sigreturn_fpregs, tc)
|
||||
{
|
||||
atf_tc_set_md_var(tc, "descr", "FP registers reloaded after sigreturn "
|
||||
"are the thread's own under preemption");
|
||||
atf_tc_set_md_var(tc, "timeout", "60");
|
||||
}
|
||||
ATF_TC_BODY(sigreturn_fpregs, tc)
|
||||
{
|
||||
double in[NREGS], out[NREGS + 1];
|
||||
struct sigaction sa;
|
||||
sigset_t set;
|
||||
pthread_t pt;
|
||||
uint64_t iters, bad, v, first_exp, first_got;
|
||||
time_t end;
|
||||
long tid;
|
||||
int first_reg, k, p, r;
|
||||
|
||||
if (pick_cpus() < 2)
|
||||
atf_tc_skip("needs two CPUs");
|
||||
pin_thread(checker_cpu);
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = handler;
|
||||
ATF_REQUIRE_EQ(0, sigaction(SIGUSR1, &sa, NULL));
|
||||
|
||||
/* The helpers never see the signal. */
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGUSR1);
|
||||
ATF_REQUIRE_EQ(0, pthread_sigmask(SIG_BLOCK, &set, NULL));
|
||||
for (p = 0; p < NPOLL; p++) {
|
||||
for (r = 0; r < 32; r++) {
|
||||
v = 0xbad0000000000000ULL | ((uint64_t)(p + 1) << 32) |
|
||||
r;
|
||||
memcpy(&pollpat[p][r], &v, sizeof(v));
|
||||
}
|
||||
ATF_REQUIRE_EQ(0, pipe(pin_in[p]));
|
||||
ATF_REQUIRE_EQ(0, pipe(pin_out[p]));
|
||||
ATF_REQUIRE_EQ(0, pthread_create(&pt, NULL, polluter,
|
||||
(void *)(intptr_t)p));
|
||||
}
|
||||
ATF_REQUIRE_EQ(0, pthread_create(&pt, NULL, waker, NULL));
|
||||
ATF_REQUIRE_EQ(0, pthread_sigmask(SIG_UNBLOCK, &set, NULL));
|
||||
|
||||
for (r = 0; r < NREGS; r++) {
|
||||
v = 0xc0de000000000000ULL | (uint64_t)(r + 14);
|
||||
memcpy(&in[r], &v, sizeof(v));
|
||||
}
|
||||
thr_self(&tid);
|
||||
|
||||
iters = bad = 0;
|
||||
first_reg = -1;
|
||||
first_exp = first_got = 0;
|
||||
end = time(NULL) + DURATION;
|
||||
while (time(NULL) < end) {
|
||||
for (k = 0; k < 2000; k++) {
|
||||
memset(out, 0, sizeof(out));
|
||||
fp_roundtrip(in, out, tid, SIGUSR1, pin_in[NPOLL - 1][1],
|
||||
pin_out[NPOLL - 1][0]);
|
||||
iters++;
|
||||
if (memcmp(in, out, sizeof(in)) == 0)
|
||||
continue;
|
||||
bad++;
|
||||
if (first_reg >= 0)
|
||||
continue;
|
||||
for (r = 0; r < NREGS; r++) {
|
||||
memcpy(&first_exp, &in[r], sizeof(first_exp));
|
||||
memcpy(&first_got, &out[r], sizeof(first_got));
|
||||
if (first_exp != first_got) {
|
||||
first_reg = r + 14;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bad != 0)
|
||||
atf_tc_fail("%ju of %ju sigreturn round trips came back with "
|
||||
"another thread's FP registers (first: f%d expected %#jx "
|
||||
"got %#jx)", (uintmax_t)bad, (uintmax_t)iters, first_reg,
|
||||
(uintmax_t)first_exp, (uintmax_t)first_got);
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, sigreturn_fpregs);
|
||||
|
||||
return (atf_no_error());
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2026 Piotr Kubaj <pkubaj@FreeBSD.org>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Helpers for sigreturn_fpregs.c, kept in assembly so that nothing touches
|
||||
* the FP registers between the sigreturn and the stores that take the lazy
|
||||
* reload trap.
|
||||
*
|
||||
* fp_roundtrip(in, out, tid, sig, wfd, rfd): load f14-f31 from in, raise
|
||||
* sig on tid with thr_kill(2), write a byte to wfd and read one from rfd
|
||||
* (both raw system calls: they let the helper on this CPU run), then store
|
||||
* f14-f31 and the FPSCR to out.
|
||||
*
|
||||
* fp_load_all(pat): load f0-f31 from pat.
|
||||
*/
|
||||
|
||||
.abiversion 2
|
||||
.text
|
||||
/* void fp_roundtrip(const double *in, double *out, long tid, long sig, long wfd, long rfd) */
|
||||
.globl fp_roundtrip
|
||||
.type fp_roundtrip,@function
|
||||
fp_roundtrip:
|
||||
mflr 0
|
||||
std 0,16(1)
|
||||
stdu 1,-320(1)
|
||||
stfd 14,32(1)
|
||||
stfd 15,40(1)
|
||||
stfd 16,48(1)
|
||||
stfd 17,56(1)
|
||||
stfd 18,64(1)
|
||||
stfd 19,72(1)
|
||||
stfd 20,80(1)
|
||||
stfd 21,88(1)
|
||||
stfd 22,96(1)
|
||||
stfd 23,104(1)
|
||||
stfd 24,112(1)
|
||||
stfd 25,120(1)
|
||||
stfd 26,128(1)
|
||||
stfd 27,136(1)
|
||||
stfd 28,144(1)
|
||||
stfd 29,152(1)
|
||||
stfd 30,160(1)
|
||||
stfd 31,168(1)
|
||||
std 28,192(1)
|
||||
std 29,200(1)
|
||||
std 30,176(1)
|
||||
std 31,184(1)
|
||||
mr 30,3
|
||||
mr 31,4
|
||||
mr 28,7
|
||||
mr 29,8
|
||||
lfd 14,0(30)
|
||||
lfd 15,8(30)
|
||||
lfd 16,16(30)
|
||||
lfd 17,24(30)
|
||||
lfd 18,32(30)
|
||||
lfd 19,40(30)
|
||||
lfd 20,48(30)
|
||||
lfd 21,56(30)
|
||||
lfd 22,64(30)
|
||||
lfd 23,72(30)
|
||||
lfd 24,80(30)
|
||||
lfd 25,88(30)
|
||||
lfd 26,96(30)
|
||||
lfd 27,104(30)
|
||||
lfd 28,112(30)
|
||||
lfd 29,120(30)
|
||||
lfd 30,128(30)
|
||||
lfd 31,136(30)
|
||||
li 0,433 /* SYS_thr_kill */
|
||||
mr 3,5
|
||||
mr 4,6
|
||||
sc
|
||||
/* ping the helper on this CPU: it leaves ITS registers in the FPU */
|
||||
li 0,4 /* SYS_write(wfd, buf, 1) */
|
||||
mr 3,28
|
||||
addi 4,1,208
|
||||
li 5,1
|
||||
sc
|
||||
li 0,3 /* SYS_read(rfd, buf, 1) */
|
||||
mr 3,29
|
||||
addi 4,1,208
|
||||
li 5,1
|
||||
sc
|
||||
/* first FP instruction since sigreturn: lazy reload happens here */
|
||||
stfd 14,0(31)
|
||||
stfd 15,8(31)
|
||||
stfd 16,16(31)
|
||||
stfd 17,24(31)
|
||||
stfd 18,32(31)
|
||||
stfd 19,40(31)
|
||||
stfd 20,48(31)
|
||||
stfd 21,56(31)
|
||||
stfd 22,64(31)
|
||||
stfd 23,72(31)
|
||||
stfd 24,80(31)
|
||||
stfd 25,88(31)
|
||||
stfd 26,96(31)
|
||||
stfd 27,104(31)
|
||||
stfd 28,112(31)
|
||||
stfd 29,120(31)
|
||||
stfd 30,128(31)
|
||||
stfd 31,136(31)
|
||||
mffs 0 /* FPSCR, for diagnosis */
|
||||
stfd 0,144(31)
|
||||
lfd 14,32(1)
|
||||
lfd 15,40(1)
|
||||
lfd 16,48(1)
|
||||
lfd 17,56(1)
|
||||
lfd 18,64(1)
|
||||
lfd 19,72(1)
|
||||
lfd 20,80(1)
|
||||
lfd 21,88(1)
|
||||
lfd 22,96(1)
|
||||
lfd 23,104(1)
|
||||
lfd 24,112(1)
|
||||
lfd 25,120(1)
|
||||
lfd 26,128(1)
|
||||
lfd 27,136(1)
|
||||
lfd 28,144(1)
|
||||
lfd 29,152(1)
|
||||
lfd 30,160(1)
|
||||
lfd 31,168(1)
|
||||
ld 28,192(1)
|
||||
ld 29,200(1)
|
||||
ld 30,176(1)
|
||||
ld 31,184(1)
|
||||
addi 1,1,320
|
||||
ld 0,16(1)
|
||||
mtlr 0
|
||||
blr
|
||||
.size fp_roundtrip,.-fp_roundtrip
|
||||
|
||||
/* void fp_load_all(const double *pat): leave pat[0..31] live in f0-f31 */
|
||||
.globl fp_load_all
|
||||
.type fp_load_all,@function
|
||||
fp_load_all:
|
||||
lfd 0,0(3)
|
||||
lfd 1,8(3)
|
||||
lfd 2,16(3)
|
||||
lfd 3,24(3)
|
||||
lfd 4,32(3)
|
||||
lfd 5,40(3)
|
||||
lfd 6,48(3)
|
||||
lfd 7,56(3)
|
||||
lfd 8,64(3)
|
||||
lfd 9,72(3)
|
||||
lfd 10,80(3)
|
||||
lfd 11,88(3)
|
||||
lfd 12,96(3)
|
||||
lfd 13,104(3)
|
||||
lfd 14,112(3)
|
||||
lfd 15,120(3)
|
||||
lfd 16,128(3)
|
||||
lfd 17,136(3)
|
||||
lfd 18,144(3)
|
||||
lfd 19,152(3)
|
||||
lfd 20,160(3)
|
||||
lfd 21,168(3)
|
||||
lfd 22,176(3)
|
||||
lfd 23,184(3)
|
||||
lfd 24,192(3)
|
||||
lfd 25,200(3)
|
||||
lfd 26,208(3)
|
||||
lfd 27,216(3)
|
||||
lfd 28,224(3)
|
||||
lfd 29,232(3)
|
||||
lfd 30,240(3)
|
||||
lfd 31,248(3)
|
||||
blr
|
||||
.size fp_load_all,.-fp_load_all
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
Reference in new issue
Block a user