#27
ntdll iOS: the orphan-lock reaper no longer releases the guest's own SRW locks (Ori and the Will of the Wisps crash)
Ori and the Will of the Wisps crashed while loading a new area. Within one monitor cycle the orphan-lock detector (ml447) logged strikes 1/3, 2/3 and 3/3 for an SRW lock that four of the game's job threads were queued on, and released it (ml446). At the same moment one of those threads threw a C++ exception nobody caught, and the game was terminated:
[lock-orphan] SRW 0x3741ee290 word=00010009 waiters=4 no-live-stamp strike=1/3 rev=ml447
[lock-orphan] SRW 0x3741ee290 word=00010009 waiters=4 no-live-stamp strike=2/3 rev=ml447
[lock-orphan] SRW 0x3741ee290 word=00010007 waiters=4 no-live-stamp strike=3/3 rev=ml447
[lock-reap] SRW 0x3741ee290 dead_teb=0xdead released exclusive (word 00010007 -> 00000006) rev=ml446
002c:err:seh:call_seh_handlers [exc] ml811 code=e06d7363 ... tid=002c hit=1
wine: Unhandled exception 0xe06d7363 in thread 2c at address 000000014880C110 (thread 002c), starting debugger...Cause
ios_orphan_check()walks the alert-waiter registry and handled a lock once per parked waiter, so a lock with four waiters collected three strikes in a single check instead of over three monitor cycles (~6 s).- "No live thread stamps it" is true for every lock of the guest: only FEX stamps TEB Instrumentation[6] (CodeBufferWriteMutex) and [8] (fexlock), each for a short critical section, and no
[stamp-set]line appeared in four sessions of the game. Any contended guest SRW lock with three or more exclusive waiters at a monitor tick could be released while its owner was inside it. Here the lock word even changed between the strikes (00010009->00010007): a waiter had just been handed the lock.
Change (ml1295)
- A lock is looked at once per check.
- A strike counts only when the lock word equals the word at the previous strike; otherwise the count starts again.
- Only a lock that some thread was once seen stamping (a FEX lock) can be reaped. Any other is left alone and logged as
[lock-orphan] SKIP ... a guest lock (never stamped), not reaped rev=ml1295(the first 8). - The dead-port reaper in Madeira's
signal_arm64_ios.c(a thread whose Mach port is dead and whose TEB names the lock) is unchanged, so the case the reaper was written for (#74, a thread terminated while holding CodeBufferWriteMutex) is still covered.
Testing
- On a Mac, with
ios_srw_reap_exclusive()andios_orphan_check()cut from this file and a real page: the crash case (a guest lock, four waiters, six checks) is not reaped, while the old code reaps it in one check, exactly as in the device log; a FEX lock seen stamped once and then held with three waiters and an unchanged word is reaped on the third check; a word change restarts the count; a live stamp prevents the reap: 7 of 7. - On the device (iPad, A16, iPadOS 26.6.1): a 507 s session of the game with this change ran without a crash. The case did not come up in it, so the new SKIP path has not been seen on the device yet.
- The unix ntdll builds with no new warnings.
After merging
Please move Madeira's wine pin and rebuild the unix ntdll (build/ntdll-unix/build.sh). Independent of willfaust/Madeira#219 and willfaust/dxmt#19.
🤖 Generated with Claude Code