mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
tests: another thread's context with its x87/SSE state, on W64 and W3
tests/wine64's unixctx case: a thread with known xmm6-xmm15 inside a Unix call (ws2_32's gethostname), then in guest code, suspended 2000 times each and read with CONTEXT_FLOATING_POINT; each phase ends with a SetThreadContext changing xmm15, which the thread must see. tests/w3/ suspctx.sh: the same in guest code on W3, xmm0-xmm7, MXCSR and CONTEXT_EXTENDED_REGISTERS, natively and in the page's runtime. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
This commit is contained in:
1 parent
52d2b58cca
commit
865c1b20d2
4 files changed
+326
No files matched your search
@@ -0,0 +1,111 @@
|
||||
/* Another thread's context, with its x87/SSE state, while the thread runs
|
||||
* guest code (H: W3's suspend path published only the integer, control and
|
||||
* segment registers, so the floating-point state came back as zeros). A
|
||||
* thread spins in a loop that touches no FP register, with xmm0-xmm7 and
|
||||
* MXCSR holding known values; another suspends it again and again and reads
|
||||
* its context with CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS.
|
||||
* Every context must show those values. Last, a SetThreadContext changes
|
||||
* xmm7 in a suspended thread, and the thread must see the new value. */
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
static void check(int ok, const char *what, double got)
|
||||
{
|
||||
if (ok) printf("ok %s (%.0f)\n", what, got);
|
||||
else { printf("FAIL %s (%.0f)\n", what, got); failures++; }
|
||||
}
|
||||
|
||||
/* spin(regs, stop, count): xmm0-xmm7 = regs[0..7], MXCSR = regs[16]'s
|
||||
* first dword, then count++ until *stop, then regs[8..15] = xmm0-xmm7. */
|
||||
void __cdecl spin(unsigned char *regs, volatile LONG *stop, volatile LONG *count);
|
||||
__asm__(
|
||||
".globl _spin\n"
|
||||
"_spin:\n"
|
||||
" push %ebx\n push %esi\n push %edi\n"
|
||||
" mov 16(%esp), %ebx\n mov 20(%esp), %esi\n mov 24(%esp), %edi\n"
|
||||
" movdqu 0(%ebx), %xmm0\n movdqu 16(%ebx), %xmm1\n movdqu 32(%ebx), %xmm2\n"
|
||||
" movdqu 48(%ebx), %xmm3\n movdqu 64(%ebx), %xmm4\n movdqu 80(%ebx), %xmm5\n"
|
||||
" movdqu 96(%ebx), %xmm6\n movdqu 112(%ebx), %xmm7\n"
|
||||
" ldmxcsr 256(%ebx)\n"
|
||||
"1:\n"
|
||||
" lock incl (%edi)\n"
|
||||
" mov (%esi), %eax\n test %eax, %eax\n jz 1b\n"
|
||||
" movdqu %xmm0, 128(%ebx)\n movdqu %xmm1, 144(%ebx)\n movdqu %xmm2, 160(%ebx)\n"
|
||||
" movdqu %xmm3, 176(%ebx)\n movdqu %xmm4, 192(%ebx)\n movdqu %xmm5, 208(%ebx)\n"
|
||||
" movdqu %xmm6, 224(%ebx)\n movdqu %xmm7, 240(%ebx)\n"
|
||||
" stmxcsr 260(%ebx)\n"
|
||||
" mov $0x1f80, %eax\n mov %eax, 264(%ebx)\n ldmxcsr 264(%ebx)\n"
|
||||
" pop %edi\n pop %esi\n pop %ebx\n ret\n");
|
||||
|
||||
static unsigned char regs[272];
|
||||
static volatile LONG stop, count;
|
||||
|
||||
static DWORD WINAPI spinner(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
spin(regs, &stop, &count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
HANDLE t;
|
||||
CONTEXT c;
|
||||
unsigned char want7[16];
|
||||
int i, fp = 0, xr = 0, bad = 0, taken = 0, set = 0;
|
||||
const DWORD mxcsr = 0x9fc0; /* FTZ, DAZ, every exception masked */
|
||||
|
||||
for (i = 0; i < 128; i++) regs[i] = (unsigned char)(i * 37 + 11);
|
||||
memcpy(regs + 256, &mxcsr, 4);
|
||||
t = CreateThread(NULL, 0, spinner, NULL, 0, NULL);
|
||||
while (count < 1000) Sleep(1);
|
||||
for (i = 0; i < 2000; i++)
|
||||
{
|
||||
memset(&c, 0, sizeof c);
|
||||
c.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS;
|
||||
if (SuspendThread(t) == (DWORD)-1) break;
|
||||
if (GetThreadContext(t, &c))
|
||||
{
|
||||
const BYTE *fx = c.ExtendedRegisters; /* FXSAVE: MXCSR at 24, xmm0 at 160 */
|
||||
DWORD mx;
|
||||
memcpy(&mx, fx + 24, 4);
|
||||
taken++;
|
||||
if (c.ContextFlags & CONTEXT_FLOATING_POINT & ~CONTEXT_i386) fp++;
|
||||
if (c.ContextFlags & CONTEXT_EXTENDED_REGISTERS & ~CONTEXT_i386)
|
||||
{
|
||||
xr++;
|
||||
if (memcmp(fx + 160, regs, 128) || mx != mxcsr)
|
||||
{
|
||||
if (bad < 4) printf("bad %d: mxcsr %#lx\n", i, (unsigned long)mx);
|
||||
bad++;
|
||||
}
|
||||
}
|
||||
}
|
||||
ResumeThread(t);
|
||||
if (i % 16 == 0) Sleep(0);
|
||||
}
|
||||
/* xmm7 changed in the suspended thread */
|
||||
for (i = 0; i < 16; i++) want7[i] = (unsigned char)(0xa5 ^ i);
|
||||
memset(&c, 0, sizeof c);
|
||||
c.ContextFlags = CONTEXT_FULL | CONTEXT_EXTENDED_REGISTERS;
|
||||
if (SuspendThread(t) != (DWORD)-1 && GetThreadContext(t, &c))
|
||||
{
|
||||
memcpy(c.ExtendedRegisters + 160 + 7 * 16, want7, 16);
|
||||
c.ContextFlags = CONTEXT_EXTENDED_REGISTERS;
|
||||
set = SetThreadContext(t, &c);
|
||||
ResumeThread(t);
|
||||
}
|
||||
InterlockedExchange(&stop, 1);
|
||||
WaitForSingleObject(t, INFINITE);
|
||||
check(taken == 2000, "contexts taken", taken);
|
||||
check(fp == taken && xr == taken, "contexts with the floating-point state", xr);
|
||||
check(bad == 0, "of them showing other xmm0-xmm7 or MXCSR than the thread's", bad);
|
||||
check(set && !memcmp(regs + 128, regs, 112), "xmm0-xmm6 unchanged", count);
|
||||
check(set && !memcmp(regs + 240, want7, 16), "xmm7 as SetThreadContext set it", set);
|
||||
check(!memcmp(regs + 260, &mxcsr, 4), "MXCSR unchanged", 0);
|
||||
printf("suspctx %d failures\n", failures);
|
||||
return failures != 0;
|
||||
}
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Another thread's context, with its x87/SSE state, while the thread runs
|
||||
# guest code (suspctx.c): GetThreadContext's xmm0-xmm7 and MXCSR, and a
|
||||
# SetThreadContext's xmm7, on a suspended thread. Prints "suspctx 0 failures".
|
||||
# Natively (gexc run) and in the page's runtime under Node (run-node.mjs).
|
||||
#
|
||||
# tests/w3/suspctx.sh DIST PREFIX [gexc]
|
||||
#
|
||||
# DIST is a W3 Wine install, PREFIX a Wine prefix made for it (copied, never
|
||||
# written). Needs i686-w64-mingw32-gcc, the x86 shared-memory shim and seed
|
||||
# in web/public/gex and, for the native legs, an x86 gexc.
|
||||
set -u
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$here/../.." && pwd)
|
||||
dist=$(realpath "${1:?DIST}"); prefix=$(realpath "${2:?PREFIX}")
|
||||
gexc=$(realpath -m "${3:-$root/target/x86/release/gexc}")
|
||||
w=${WORK:-$root/target/w3-suspctx}
|
||||
rm -rf "$w"; mkdir -p "$w"
|
||||
i686-w64-mingw32-gcc -O1 -o "$w/suspctx.exe" "$here/suspctx.c" || exit 1
|
||||
want="suspctx 0 failures"
|
||||
pass=0 fail=0
|
||||
verdict() { # NAME LOG
|
||||
if grep -q "^$want" "$2"; then pass=$((pass + 1)); echo "ok $1"
|
||||
else fail=$((fail + 1)); echo "FAIL $1"; grep -v '^\[host\]\|gex-console-error' "$2" | tail -8 | sed 's/^/ /'; fi
|
||||
}
|
||||
for mode in fast; do
|
||||
extra=()
|
||||
if [ -x "$gexc" ]; then
|
||||
rm -rf "$w/pfx-native"; cp -a "$prefix" "$w/pfx-native"; cp "$w/suspctx.exe" "$w/pfx-native/drive_c/"
|
||||
(cd "$w/pfx-native/drive_c" && timeout -k 10 "${TMO:-300}" "$gexc" run \
|
||||
--env WINEPREFIX="$w/pfx-native" --env WINEDEBUG=err+all,fixme-all "${extra[@]}" \
|
||||
"$dist/lib/wine/i386-unix/wine" 'C:\suspctx.exe') > "$w/native-$mode.log" 2>&1
|
||||
verdict "native $mode" "$w/native-$mode.log"
|
||||
else
|
||||
echo "skip native: no $gexc"
|
||||
fi
|
||||
for f in gex_browser.x86.shared.wasm seed.x86.shared.wasm; do
|
||||
[ -f "$root/web/public/gex/$f" ] || { echo "skip page: no web/public/gex/$f"; continue 2; }
|
||||
done
|
||||
rm -rf "$w/pfx-page" "$w/t"; cp -a "$prefix" "$w/pfx-page"; mkdir -p "$w/t"
|
||||
(cd "$root" && GEX_ARCH=x86 GEX_SHARED=1 timeout -k 10 "${TMO:-300}" node --max-old-space-size=8192 \
|
||||
shims/browser/js/run-node.mjs --dir /w3="$dist" --dir /pfx="$w/pfx-page" --dir /t="$w/t" \
|
||||
--file "/t/suspctx.exe=$w/suspctx.exe" --env WINEPREFIX=/pfx --env WINEDEBUG=err+all,fixme-all "${extra[@]}" \
|
||||
--cwd /t --argv0 /w3/lib/wine/i386-unix/wine web/public/gex/seed.x86.shared.wasm \
|
||||
"$dist/lib/wine/i386-unix/wine" /t/suspctx.exe) > "$w/page-$mode.log" 2>&1
|
||||
verdict "page $mode" "$w/page-$mode.log"
|
||||
done
|
||||
echo "$pass passed, $fail failed"
|
||||
[ "$fail" = 0 ]
|
||||
@@ -38,6 +38,8 @@
|
||||
# The audio case: waveOut and mmdevapi through wineoss.drv (audio.c).
|
||||
# The authattrs case: crypt32 verifies a signature over authenticated
|
||||
# attributes as encoded, not in DER order (authattrs.c).
|
||||
# The unixctx case: another thread's context, with its SSE state, while the
|
||||
# thread is inside a Unix call and while it runs guest code (unixctx.c).
|
||||
# The qpcmono case: the performance counter, monotonic across the PE and
|
||||
# Unix sides (qpcmono.c).
|
||||
# The dwrite case: DirectWrite's glyph bounds, alpha texture and outline
|
||||
@@ -426,6 +428,16 @@ else
|
||||
case " ${CASES:-qpcmono} " in *" qpcmono "*)
|
||||
fail=$((fail + 1)); echo "FAIL x64/qpcmono ($tag): x86_64-w64-mingw32-gcc" ;; esac
|
||||
fi
|
||||
# Another thread's context taken while the thread is inside a Unix call, and
|
||||
# while it runs guest code (unixctx.c): its xmm6-xmm15 as the thread has them,
|
||||
# though the Unix call's frame leaves the x87/SSE state in gex's context until
|
||||
# something reads it, and a SetThreadContext's xmm15 reaching the thread.
|
||||
if x86_64-w64-mingw32-gcc -O1 -o "$w/unixctx.exe" "$here/unixctx.c" -lws2_32; then
|
||||
check unixctx 0 "$allok" "$w/unixctx.exe"
|
||||
else
|
||||
case " ${CASES:-unixctx} " in *" unixctx "*)
|
||||
fail=$((fail + 1)); echo "FAIL x64/unixctx ($tag): x86_64-w64-mingw32-gcc" ;; esac
|
||||
fi
|
||||
# A skeleton's view unmapped and its addresses reused for the program's own
|
||||
# code (stalebind.c): the native bindings must not outlive the view. Only
|
||||
# with the native PE core, whose DLLs are skeletons (NATIVE=).
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/* Another thread's context, taken while it is inside a Unix call (H, B1:
|
||||
* the Unix call's frame leaves the x87/SSE state in gex's context until
|
||||
* something reads it). A thread loops on gethostname -- ws2_32's Unix call
|
||||
* -- with xmm6-xmm15 holding known values, which it never changes (they are
|
||||
* callee-saved); another suspends it again and again and reads its context
|
||||
* with CONTEXT_FLOATING_POINT. Every context must carry the floating-point
|
||||
* state and show those values, and the loop must end with them unchanged.
|
||||
* Then the same with a loop that makes no call (guest code: the suspend
|
||||
* signal's own context). Each phase ends with a SetThreadContext changing
|
||||
* xmm15, which the thread must see. */
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
static void check(int ok, const char *what, double got)
|
||||
{
|
||||
if (ok) printf("ok %s (%.0f)\n", what, got);
|
||||
else { printf("FAIL %s (%.0f)\n", what, got); failures++; }
|
||||
}
|
||||
|
||||
/* spin(regs, stop, fn, buf): xmm6-xmm15 = regs[0..9], then fn(buf, 256)
|
||||
* (or, with no fn, a count of calls_started) until *stop, then
|
||||
* regs[10..19] = xmm6-xmm15. */
|
||||
void spin(unsigned char *regs, volatile LONG *stop, int (WINAPI *fn)(char *, int), char *buf);
|
||||
__asm__(
|
||||
".globl spin\n"
|
||||
"spin:\n"
|
||||
" push %rbx\n push %rsi\n push %rdi\n push %r12\n"
|
||||
" sub $200, %rsp\n"
|
||||
" movdqu %xmm6, 32(%rsp)\n movdqu %xmm7, 48(%rsp)\n movdqu %xmm8, 64(%rsp)\n"
|
||||
" movdqu %xmm9, 80(%rsp)\n movdqu %xmm10, 96(%rsp)\n movdqu %xmm11, 112(%rsp)\n"
|
||||
" movdqu %xmm12, 128(%rsp)\n movdqu %xmm13, 144(%rsp)\n movdqu %xmm14, 160(%rsp)\n"
|
||||
" movdqu %xmm15, 176(%rsp)\n"
|
||||
" mov %rcx, %rbx\n mov %rdx, %rsi\n mov %r8, %rdi\n mov %r9, %r12\n"
|
||||
" movdqu 0(%rbx), %xmm6\n movdqu 16(%rbx), %xmm7\n movdqu 32(%rbx), %xmm8\n"
|
||||
" movdqu 48(%rbx), %xmm9\n movdqu 64(%rbx), %xmm10\n movdqu 80(%rbx), %xmm11\n"
|
||||
" movdqu 96(%rbx), %xmm12\n movdqu 112(%rbx), %xmm13\n movdqu 128(%rbx), %xmm14\n"
|
||||
" movdqu 144(%rbx), %xmm15\n"
|
||||
"1:\n"
|
||||
" test %rdi, %rdi\n jz 2f\n"
|
||||
" mov %r12, %rcx\n mov $256, %edx\n call *%rdi\n"
|
||||
" jmp 3f\n"
|
||||
"2:\n lock incl calls_started(%rip)\n"
|
||||
"3:\n"
|
||||
" mov (%rsi), %eax\n test %eax, %eax\n jz 1b\n"
|
||||
" movdqu %xmm6, 160(%rbx)\n movdqu %xmm7, 176(%rbx)\n movdqu %xmm8, 192(%rbx)\n"
|
||||
" movdqu %xmm9, 208(%rbx)\n movdqu %xmm10, 224(%rbx)\n movdqu %xmm11, 240(%rbx)\n"
|
||||
" movdqu %xmm12, 256(%rbx)\n movdqu %xmm13, 272(%rbx)\n movdqu %xmm14, 288(%rbx)\n"
|
||||
" movdqu %xmm15, 304(%rbx)\n"
|
||||
" movdqu 32(%rsp), %xmm6\n movdqu 48(%rsp), %xmm7\n movdqu 64(%rsp), %xmm8\n"
|
||||
" movdqu 80(%rsp), %xmm9\n movdqu 96(%rsp), %xmm10\n movdqu 112(%rsp), %xmm11\n"
|
||||
" movdqu 128(%rsp), %xmm12\n movdqu 144(%rsp), %xmm13\n movdqu 160(%rsp), %xmm14\n"
|
||||
" movdqu 176(%rsp), %xmm15\n"
|
||||
" add $200, %rsp\n pop %r12\n pop %rdi\n pop %rsi\n pop %rbx\n ret\n");
|
||||
|
||||
static unsigned char regs[320];
|
||||
static volatile LONG stop;
|
||||
static char host[256];
|
||||
volatile LONG calls_started;
|
||||
|
||||
static int WINAPI counted_gethostname(char *buf, int len)
|
||||
{
|
||||
InterlockedIncrement(&calls_started);
|
||||
return gethostname(buf, len);
|
||||
}
|
||||
|
||||
static DWORD WINAPI spinner(void *arg)
|
||||
{
|
||||
spin(regs, &stop, arg, host);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* one phase: the spinner with FN (in the Unix call) or without (guest code),
|
||||
* 2000 suspended contexts, then xmm15 changed by SetThreadContext */
|
||||
static void phase(const char *where, int (WINAPI *fn)(char *, int))
|
||||
{
|
||||
unsigned char want15[16];
|
||||
char what[128];
|
||||
HANDLE t;
|
||||
CONTEXT c;
|
||||
int i, fp = 0, bad = 0, taken = 0, set = 0;
|
||||
|
||||
stop = 0; calls_started = 0;
|
||||
memset(regs + 160, 0, 160);
|
||||
t = CreateThread(NULL, 0, spinner, fn, 0, NULL);
|
||||
while (calls_started < 100) Sleep(1);
|
||||
for (i = 0; i < 2000; i++)
|
||||
{
|
||||
memset(&c, 0, sizeof c);
|
||||
c.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
|
||||
if (SuspendThread(t) == (DWORD)-1) break;
|
||||
if (GetThreadContext(t, &c))
|
||||
{
|
||||
taken++;
|
||||
if ((c.ContextFlags & CONTEXT_FLOATING_POINT & ~CONTEXT_AMD64))
|
||||
{
|
||||
fp++;
|
||||
if (memcmp(&c.Xmm6, regs, 160))
|
||||
{
|
||||
HMODULE m = NULL;
|
||||
char name[MAX_PATH] = "?";
|
||||
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
(void *)c.Rip, &m);
|
||||
if (m) GetModuleFileNameA(m, name, sizeof name);
|
||||
if (bad < 4) printf("bad %d: at %s+%#llx, mxcsr %#lx\n", i, strrchr(name, '\\') ? strrchr(name, '\\') + 1 : name,
|
||||
(unsigned long long)(c.Rip - (DWORD64)m), (unsigned long)c.MxCsr);
|
||||
bad++;
|
||||
}
|
||||
}
|
||||
}
|
||||
ResumeThread(t);
|
||||
if (i % 16 == 0) Sleep(0);
|
||||
}
|
||||
for (i = 0; i < 16; i++) want15[i] = (unsigned char)(0xa5 ^ i);
|
||||
memset(&c, 0, sizeof c);
|
||||
c.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
|
||||
if (SuspendThread(t) != (DWORD)-1 && GetThreadContext(t, &c))
|
||||
{
|
||||
memcpy(&c.Xmm15, want15, 16);
|
||||
c.ContextFlags = CONTEXT_FLOATING_POINT;
|
||||
set = SetThreadContext(t, &c);
|
||||
ResumeThread(t);
|
||||
}
|
||||
InterlockedExchange(&stop, 1);
|
||||
WaitForSingleObject(t, INFINITE);
|
||||
CloseHandle(t);
|
||||
sprintf(what, "%s: contexts taken", where);
|
||||
check(taken == 2000, what, taken);
|
||||
sprintf(what, "%s: contexts with the floating-point state", where);
|
||||
check(fp == taken, what, fp);
|
||||
sprintf(what, "%s: of them showing other xmm6-xmm15 than the thread's", where);
|
||||
check(bad == 0, what, bad);
|
||||
sprintf(what, "%s: xmm6-xmm14 unchanged across the loop", where);
|
||||
check(!memcmp(regs + 160, regs, 144), what, calls_started);
|
||||
sprintf(what, "%s: xmm15 as SetThreadContext set it", where);
|
||||
check(set && !memcmp(regs + 304, want15, 16), what, set);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
WSADATA wsa;
|
||||
int i;
|
||||
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
for (i = 0; i < 160; i++) regs[i] = (unsigned char)(i * 37 + 11);
|
||||
check(gethostname(host, sizeof host) == 0, "gethostname works", 0);
|
||||
phase("unix call", counted_gethostname);
|
||||
phase("guest code", NULL);
|
||||
printf("%d failures\n", failures);
|
||||
return failures != 0;
|
||||
}
|
||||
Reference in new issue
Block a user