From 9cdc8550ece2e83ba112b63d6c4fef7a90a767d0 Mon Sep 17 00:00:00 2001 From: StealUrKill <35749471+StealUrKill@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:29:17 -0500 Subject: [PATCH] [pthread] Fix cond-var lock inversion and POSIX timedwait ABI (upstream #113) (#115) Two follow-ups to upstream #102's condition-variable changes, as analyzed in upstream issue #113: - The pending-signal consume path reacquired the guest mutex while still holding the condition state lock, inverting lock order against cond-signal (mutex -> SyncRoot) and deadlocking both threads. Leave the condition lock before relocking, matching the normal wake path. This unfroze Dreaming Sarah (PPSA02929) at its title screen. - pthread_cond_timedwait's third argument is a pointer to an absolute CLOCK_REALTIME timespec, not a relative microsecond count; the guest address was being truncated into a duration, yielding arbitrary timeouts. Read the timespec and convert to a relative wait. scePthreadCondTimedwait keeps its separate relative-time ABI. --- .../Kernel/KernelPthreadCompatExports.cs | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs index 8c89b4f..8446b26 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs @@ -349,7 +349,32 @@ public static class KernelPthreadCompatExports ExportName = "pthread_cond_timedwait", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")] - public static int PosixPthreadCondTimedwait(CpuContext ctx) => PthreadCondTimedwait(ctx); + public static int PosixPthreadCondTimedwait(CpuContext ctx) + { + // POSIX passes `const struct timespec *abstime` (absolute deadline), not relative microseconds (#113). + var abstimeAddress = ctx[CpuRegister.Rdx]; + if (abstimeAddress == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!ctx.TryReadUInt64(abstimeAddress, out var deadlineSeconds) || + !ctx.TryReadUInt64(abstimeAddress + 8, out var deadlineNanoseconds)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var nowMicroseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1000L; + var deadlineMicroseconds = + deadlineSeconds >= long.MaxValue / 1_000_000UL + ? long.MaxValue + : (long)(deadlineSeconds * 1_000_000UL + Math.Min(deadlineNanoseconds, 999_999_999UL) / 1000UL); + var remainingMicroseconds = deadlineMicroseconds - nowMicroseconds; + var timeoutUsec = remainingMicroseconds <= 0 + ? 1u + : (uint)Math.Min(remainingMicroseconds, uint.MaxValue); + return PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true, timeoutUsec: timeoutUsec); + } [SysAbiExport( Nid = "kDh-NfxgMtE", @@ -1185,10 +1210,19 @@ public static class KernelPthreadCompatExports if (consumedPendingSignal) { + // Leave SyncRoot before relocking the mutex to avoid lock-order inversion with cond-signal (#113). state.Waiters = Math.Max(0, state.Waiters - 1); TracePthreadCond("wait-wake-pending", condAddress, mutexAddress, state, timed, waitResult); - var pendingLockResult = PthreadMutexRelockForCondWait(ctx, mutexAddress, releasedRecursion); - return pendingLockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK ? pendingLockResult : waitResult; + Monitor.Exit(state.SyncRoot); + try + { + var pendingLockResult = PthreadMutexRelockForCondWait(ctx, mutexAddress, releasedRecursion); + return pendingLockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK ? pendingLockResult : waitResult; + } + finally + { + Monitor.Enter(state.SyncRoot); + } } var scheduler = GuestThreadExecution.Scheduler;