#27

ntdll iOS: the orphan-lock reaper no longer releases the guest's own SRW locks (Ori and the Will of the Wisps crash)

Openspitefulowlwants to merge intofrom

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() and ios_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