fix(libkernel): keep ymm upper halves across a redirected raise
A thread that sceKernelRaiseException redirects resumes through RtlRestoreContext with a CONTEXT that has no XSTATE, so the upper halves of ymm0-ymm15 came back as the handler and the delivery path left them. A handler or host routine that runs vzeroupper or any VEX instruction (Zen 2 guest code, the CRT's memcpy) cleared the upper lanes of a thread interrupted in the middle of AVX code. The entry stub now stores ymm0-ymm15 next to the delivery when the CPU and the OS enable AVX, and the exit loads them back right before RtlRestoreContext, which restores only the low halves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
This commit is contained in:
parent
a5df1a874d
commit
15c1962e82
|
|
@ -16,6 +16,7 @@
|
|||
extern "C" Pthread APS5_VABI scePthreadSelf();
|
||||
#ifdef _WIN32
|
||||
extern "C" void Aps5RedirectedEntryStub();
|
||||
extern "C" [[noreturn]] void Aps5RedirectedExit(CONTEXT* context, const void* vectors, void (*restore)(CONTEXT*, EXCEPTION_RECORD*));
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
|
@ -101,13 +102,21 @@ GuestExceptionHandler Handler(int signum) {
|
|||
#ifdef _WIN32
|
||||
constexpr std::size_t RedZone = 128;
|
||||
constexpr std::size_t HomeArea = 32;
|
||||
constexpr std::size_t VectorBytes = 16 * 32;
|
||||
|
||||
struct Delivery {
|
||||
GuestExceptionHandler handler;
|
||||
int signum;
|
||||
CONTEXT context;
|
||||
std::uint8_t vectors[VectorBytes];
|
||||
std::uint64_t saveVectors;
|
||||
};
|
||||
|
||||
const bool SaveVectors = [] {
|
||||
__builtin_cpu_init();
|
||||
return __builtin_cpu_supports("avx") != 0;
|
||||
}();
|
||||
|
||||
void Deliver(GuestExceptionHandler handler, int signum, CONTEXT& context) {
|
||||
GuestUcontext ucontext{};
|
||||
auto& m = ucontext.mcontext;
|
||||
|
|
@ -159,8 +168,7 @@ void Deliver(GuestExceptionHandler handler, int signum, CONTEXT& context) {
|
|||
[[noreturn]] void RedirectedEntry(Delivery* delivery) {
|
||||
CONTEXT context = delivery->context;
|
||||
Deliver(delivery->handler, delivery->signum, context);
|
||||
RtlRestoreContext(&context, nullptr);
|
||||
std::abort();
|
||||
Aps5RedirectedExit(&context, delivery->saveVectors != 0 ? delivery->vectors : nullptr, RtlRestoreContext);
|
||||
}
|
||||
|
||||
bool StackWritable(DWORD64 low, DWORD64 high) {
|
||||
|
|
@ -185,6 +193,7 @@ void CALLBACK WaitingEntry(ULONG_PTR parameter) {
|
|||
|
||||
static_assert(HomeArea + 8 == 40, "Aps5RedirectedEntryStub finds the delivery 40 bytes above its stack pointer");
|
||||
static_assert(offsetof(Delivery, context) == 16 && offsetof(CONTEXT, Rax) == 0x78 && offsetof(CONTEXT, Rbp) == 0xa0 && offsetof(CONTEXT, R15) == 0xf0, "Aps5RedirectedEntryStub stores the live registers into the delivery's context");
|
||||
static_assert(offsetof(Delivery, vectors) == 1248 && offsetof(Delivery, saveVectors) == 1760, "Aps5RedirectedEntryStub stores ymm0-ymm15 into the delivery when saveVectors is set");
|
||||
|
||||
bool Exited(HANDLE native) {
|
||||
return WaitForSingleObject(native, 0) == WAIT_OBJECT_0;
|
||||
|
|
@ -198,7 +207,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
|
|||
return true;
|
||||
}
|
||||
const auto native = static_cast<HANDLE>(thread->nativeHandle);
|
||||
auto queued = std::make_unique<Delivery>(Delivery{handler, signum, {}});
|
||||
auto queued = std::make_unique<Delivery>(Delivery{handler, signum, {}, {}, 0});
|
||||
if (SuspendThread(native) == static_cast<DWORD>(-1)) {
|
||||
if (Exited(native)) return false;
|
||||
throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread");
|
||||
|
|
@ -214,7 +223,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) {
|
|||
queued.release();
|
||||
return true;
|
||||
}
|
||||
alignas(16) Delivery delivery{handler, signum, {}};
|
||||
alignas(16) Delivery delivery{handler, signum, {}, {}, SaveVectors ? 1u : 0u};
|
||||
delivery.context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
|
||||
if (!GetThreadContext(native, &delivery.context)) {
|
||||
ResumeThread(native);
|
||||
|
|
@ -262,8 +271,50 @@ asm(".text\n"
|
|||
" movq %r13, 280(%rsp)\n"
|
||||
" movq %r14, 288(%rsp)\n"
|
||||
" movq %r15, 296(%rsp)\n"
|
||||
" cmpq $0, 1800(%rsp)\n"
|
||||
" je 1f\n"
|
||||
" vmovdqu %ymm0, 1288(%rsp)\n"
|
||||
" vmovdqu %ymm1, 1320(%rsp)\n"
|
||||
" vmovdqu %ymm2, 1352(%rsp)\n"
|
||||
" vmovdqu %ymm3, 1384(%rsp)\n"
|
||||
" vmovdqu %ymm4, 1416(%rsp)\n"
|
||||
" vmovdqu %ymm5, 1448(%rsp)\n"
|
||||
" vmovdqu %ymm6, 1480(%rsp)\n"
|
||||
" vmovdqu %ymm7, 1512(%rsp)\n"
|
||||
" vmovdqu %ymm8, 1544(%rsp)\n"
|
||||
" vmovdqu %ymm9, 1576(%rsp)\n"
|
||||
" vmovdqu %ymm10, 1608(%rsp)\n"
|
||||
" vmovdqu %ymm11, 1640(%rsp)\n"
|
||||
" vmovdqu %ymm12, 1672(%rsp)\n"
|
||||
" vmovdqu %ymm13, 1704(%rsp)\n"
|
||||
" vmovdqu %ymm14, 1736(%rsp)\n"
|
||||
" vmovdqu %ymm15, 1768(%rsp)\n"
|
||||
"1:\n"
|
||||
" leaq 40(%rsp), %rcx\n"
|
||||
" jmp Aps5RedirectedEntry\n");
|
||||
" jmp Aps5RedirectedEntry\n"
|
||||
".globl Aps5RedirectedExit\n"
|
||||
"Aps5RedirectedExit:\n"
|
||||
" testq %rdx, %rdx\n"
|
||||
" je 1f\n"
|
||||
" vmovdqu 0(%rdx), %ymm0\n"
|
||||
" vmovdqu 32(%rdx), %ymm1\n"
|
||||
" vmovdqu 64(%rdx), %ymm2\n"
|
||||
" vmovdqu 96(%rdx), %ymm3\n"
|
||||
" vmovdqu 128(%rdx), %ymm4\n"
|
||||
" vmovdqu 160(%rdx), %ymm5\n"
|
||||
" vmovdqu 192(%rdx), %ymm6\n"
|
||||
" vmovdqu 224(%rdx), %ymm7\n"
|
||||
" vmovdqu 256(%rdx), %ymm8\n"
|
||||
" vmovdqu 288(%rdx), %ymm9\n"
|
||||
" vmovdqu 320(%rdx), %ymm10\n"
|
||||
" vmovdqu 352(%rdx), %ymm11\n"
|
||||
" vmovdqu 384(%rdx), %ymm12\n"
|
||||
" vmovdqu 416(%rdx), %ymm13\n"
|
||||
" vmovdqu 448(%rdx), %ymm14\n"
|
||||
" vmovdqu 480(%rdx), %ymm15\n"
|
||||
"1:\n"
|
||||
" xorl %edx, %edx\n"
|
||||
" jmp *%r8\n");
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ static std::atomic<int> calls{0};
|
|||
static std::atomic<std::thread::id> handlerThread;
|
||||
static std::atomic<std::uint64_t> handlerRsp{0};
|
||||
static std::atomic<std::uintptr_t> handlerFrame{0};
|
||||
static std::atomic<bool> clobberVectors{false};
|
||||
|
||||
static void APS5_VABI Handler(int signum, void* context) {
|
||||
Require(signum == SIGUSR1);
|
||||
|
|
@ -41,6 +42,7 @@ static void APS5_VABI Handler(int signum, void* context) {
|
|||
handlerFrame.store(reinterpret_cast<std::uintptr_t>(&local));
|
||||
handlerRsp.store(rsp);
|
||||
handlerThread.store(std::this_thread::get_id());
|
||||
if (clobberVectors.load()) asm volatile("vzeroall" ::: "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
|
||||
calls.fetch_add(1);
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +104,60 @@ static void* APS5_VABI Leaving(void* arg) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static constexpr int VectorRounds = 100;
|
||||
static constexpr std::size_t VectorBytes = 16 * 32;
|
||||
alignas(32) static std::uint8_t vectorPattern[VectorBytes];
|
||||
alignas(32) static std::uint8_t vectorResult[VectorBytes];
|
||||
static volatile std::uint8_t vectorStop = 0;
|
||||
|
||||
static void* APS5_VABI Vectors(void* arg) {
|
||||
auto& worker = *static_cast<Worker*>(arg);
|
||||
worker.id = std::this_thread::get_id();
|
||||
worker.started.store(true);
|
||||
asm volatile(
|
||||
"vmovdqu 0(%[pattern]), %%ymm0\n"
|
||||
"vmovdqu 32(%[pattern]), %%ymm1\n"
|
||||
"vmovdqu 64(%[pattern]), %%ymm2\n"
|
||||
"vmovdqu 96(%[pattern]), %%ymm3\n"
|
||||
"vmovdqu 128(%[pattern]), %%ymm4\n"
|
||||
"vmovdqu 160(%[pattern]), %%ymm5\n"
|
||||
"vmovdqu 192(%[pattern]), %%ymm6\n"
|
||||
"vmovdqu 224(%[pattern]), %%ymm7\n"
|
||||
"vmovdqu 256(%[pattern]), %%ymm8\n"
|
||||
"vmovdqu 288(%[pattern]), %%ymm9\n"
|
||||
"vmovdqu 320(%[pattern]), %%ymm10\n"
|
||||
"vmovdqu 352(%[pattern]), %%ymm11\n"
|
||||
"vmovdqu 384(%[pattern]), %%ymm12\n"
|
||||
"vmovdqu 416(%[pattern]), %%ymm13\n"
|
||||
"vmovdqu 448(%[pattern]), %%ymm14\n"
|
||||
"vmovdqu 480(%[pattern]), %%ymm15\n"
|
||||
"1:\n"
|
||||
"pause\n"
|
||||
"cmpb $0, (%[stop])\n"
|
||||
"je 1b\n"
|
||||
"vmovdqu %%ymm0, 0(%[result])\n"
|
||||
"vmovdqu %%ymm1, 32(%[result])\n"
|
||||
"vmovdqu %%ymm2, 64(%[result])\n"
|
||||
"vmovdqu %%ymm3, 96(%[result])\n"
|
||||
"vmovdqu %%ymm4, 128(%[result])\n"
|
||||
"vmovdqu %%ymm5, 160(%[result])\n"
|
||||
"vmovdqu %%ymm6, 192(%[result])\n"
|
||||
"vmovdqu %%ymm7, 224(%[result])\n"
|
||||
"vmovdqu %%ymm8, 256(%[result])\n"
|
||||
"vmovdqu %%ymm9, 288(%[result])\n"
|
||||
"vmovdqu %%ymm10, 320(%[result])\n"
|
||||
"vmovdqu %%ymm11, 352(%[result])\n"
|
||||
"vmovdqu %%ymm12, 384(%[result])\n"
|
||||
"vmovdqu %%ymm13, 416(%[result])\n"
|
||||
"vmovdqu %%ymm14, 448(%[result])\n"
|
||||
"vmovdqu %%ymm15, 480(%[result])\n"
|
||||
"vzeroupper\n"
|
||||
:
|
||||
: [pattern] "r"(vectorPattern), [result] "r"(vectorResult), [stop] "r"(&vectorStop)
|
||||
: "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::atomic<bool> finishedReturned{false};
|
||||
|
||||
static void* APS5_VABI Finished(void*) {
|
||||
|
|
@ -191,6 +247,24 @@ int main() {
|
|||
Require(scePthreadJoin(leavingThread, nullptr) == 0);
|
||||
Require(sceKernelDeleteSema(leaving.sem) == 0);
|
||||
|
||||
if (__builtin_cpu_supports("avx")) {
|
||||
for (std::size_t i = 0; i < VectorBytes; ++i) vectorPattern[i] = static_cast<std::uint8_t>(i * 7 + 1);
|
||||
clobberVectors.store(true);
|
||||
Worker vectors;
|
||||
Pthread vectorsThread = nullptr;
|
||||
Require(scePthreadCreate(&vectorsThread, nullptr, Vectors, &vectors, "vectors") == 0);
|
||||
while (!vectors.started.load()) std::this_thread::yield();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
for (int raised = 0; raised < VectorRounds; ++raised) {
|
||||
Require(sceKernelRaiseException(vectorsThread, SIGUSR1) == 0);
|
||||
ExpectDelivery(1 + 2 * Repeats + HostRounds + LeavingRounds + raised, vectors.id);
|
||||
}
|
||||
clobberVectors.store(false);
|
||||
vectorStop = 1;
|
||||
Require(scePthreadJoin(vectorsThread, nullptr) == 0);
|
||||
Require(std::memcmp(vectorPattern, vectorResult, VectorBytes) == 0);
|
||||
}
|
||||
|
||||
Pthread finishedThread = nullptr;
|
||||
Require(scePthreadCreate(&finishedThread, nullptr, Finished, nullptr, "finished") == 0);
|
||||
while (!finishedReturned.load()) std::this_thread::yield();
|
||||
|
|
|
|||
|
|
@ -237,7 +237,6 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) delivers on Windows only and throws on Linux
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) does not consult the target thread's signal mask
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) saves and restores only `CONTEXT_FULL | CONTEXT_FLOATING_POINT` on Windows, so the upper halves of the YMM registers are not preserved if the handler or the entry uses AVX
|
||||
- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) reaches a thread that waits in an alertable `TimedWait` wait through an APC. A thread blocked anywhere else, including all waits under `APS5_COARSE_TIMED_WAITS` and host waits inside the emulator, is redirected and runs the handler only when that wait returns
|
||||
- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`.
|
||||
- [Thread cancellation](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) is not modelled: `scePthreadCancel` throws, so no cancel request is ever pending and `scePthreadTestcancel` and `pthread_testcancel` return without acting; the cancel state and type set by `scePthreadSetcancelstate` and `scePthreadSetcanceltype` are stored and not used
|
||||
|
|
|
|||
Loading…
Reference in New Issue