[Bug 60281] New: 32-bit installer crashes with stack overflow due to infinite loop in RtlUnwindEx (SEH unwinding) on macOS WoW64 build
http://bugs.winehq.org/show_bug.cgi?id=60281 Bug ID: 60281 Summary: 32-bit installer crashes with stack overflow due to infinite loop in RtlUnwindEx (SEH unwinding) on macOS WoW64 build Product: Wine Version: 11.16 Hardware: x86-64 OS: MacOS Status: UNCONFIRMED Severity: normal Priority: P2 Component: -unknown Assignee: wine-bugs@list.winehq.org Reporter: nileshkumbhar1604@gmail.com Target Milestone: --- Wine version: wine-11.16 (self-built from git, commit gcd22bd7383a) Build configuration: ./configure --enable-archs=i386,x86_64 --with-mingw Host: Mac mini M4 (Apple Silicon), running Wine under Rosetta 2 as x86_64. Steps to reproduce: 1. Build Wine from source with WoW64 support (`--enable-archs=i386,x86_64`). 2. Run a 32-bit Windows installer, for example: `./wine UltraViewer_setup_6.6.124_en.exe` 3. The installation starts and the "Installing UltraViewer" dialog appears. 4. The process then crashes with: `err:virtual:virtual_setup_exception stack overflow 1664 bytes addr 0x7bcfccd3 stack 0x130000-0x131000-0x230000` Running with `WINEDEBUG=+seh,+tid` shows that `RtlUnwindEx` is repeatedly called with the same `target_ip` (`00006FFFFDFB2E80`) and does not appear to make any progress. This continues until the thread stack overflows. I have attached the full trace. This initially looked similar to bug 58698, which also involved an infinite loop with the new WoW64 implementation. However, that issue was related to the `VirtualAlloc` size-probing logic and was fixed by commit `f4c5b04148db5fc4e5265beec461d3b7d9f4a789`. The problem described here still occurs with Wine 11.16, so it does not appear to be the same issue. A 64-bit-only build (`--enable-win64`, without WoW64) does not reproduce the problem, and 64-bit applications run normally. The issue therefore appears to be specific to the 32-bit-on-64-bit exception/unwinding path. It may also be related to the macOS/Apple Silicon environment, although I have not confirmed this yet. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60281 Ken Sharp <imwellcushtymelike@gmail.com> changed: What |Removed |Added ---------------------------------------------------------------------------- Summary|32-bit installer crashes |UltraViewer installer |with stack overflow due to |crashes on new WoW64 |infinite loop in | |RtlUnwindEx (SEH unwinding) | |on macOS WoW64 build | Keywords| |download, wow64 URL| |https://www.ultraviewer.net | |/en/UltraViewer_setup_6.6_e | |n.exe -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60281 --- Comment #1 from Nilesh <nileshkumbhar1604@gmail.com> --- I dug into RtlUnwindEx() in dlls/ntdll/signal_x86_64.c a bit and think I might have found something. The stack overflow message ("virtual_setup_exception stack overflow...") shows up right before RtlUnwindEx gets called. So by the time unwinding starts, the stack is basically already full. Inside the function there's a loop that calls call_unwind_handler() on each frame - and my guess is that this call itself needs some stack space, which it doesn't have, so it throws another stack overflow exception mid-unwind. That would explain why target_ip never changes in the trace - it's not really looping normally, it's re-entering the same unwind attempt over and over because each attempt fails the same way. If that's right, this isn't a plain infinite loop bug, it's more like the guard page / stack overflow recovery path not leaving enough headroom for the unwind handler to actually run. Not sure if this is specific to how WoW64 sets up the stack on macOS/Rosetta or if it's more general. Would appreciate it if someone who knows this code better could confirm if I'm on the right track, or point me toward what to check next. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60281 Victor <victormorel.pro@gmail.com> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |victormorel.pro@gmail.com --- Comment #2 from Victor <victormorel.pro@gmail.com> --- Independent reproduction on a different application, plus a workaround that seems to support Nilesh's headroom hypothesis in comment 1. Environment ----------- Wine 11.16 (Staging), Homebrew wine@staging cask, 64-bit prefix with the new WoW64. Mac17,2 (Apple M5), macOS 26.5.2 build 25F84, x86-64 under Rosetta 2. The application is a 32-bit Direct3D 9 game from 2013 (PE32, LargeAddressAware), not an installer, so the trigger is not specific to one program. Note the hardware differs from the original report — M5 here, M4 there — which suggests the problem is not tied to one chip generation. Symptom ------- The application fails to start roughly half the time. It dies during Direct3D initialisation, and two thirds of those failures are silent — no application crash report, no macOS diagnostic report. Over 20 preserved attempts: 10 died during D3D init, 3 crashed after reaching the login screen, 7 succeeded. stderr carries the same family of messages as this bug: err:virtual:virtual_setup_exception stack overflow 512 bytes err:virtual:virtual_setup_exception stack overflow 704 bytes err:virtual:virtual_setup_exception stack overflow 1088 bytes err:virtual:virtual_setup_exception nested exception on signal stack err:seh:call_seh_handlers invalid frame 000000000022B8C0 (0000000100112000-000000010020FD20) err:seh:NtRaiseException Exception frame is not in stack limits => unable to dispatch exception We do not see the RtlUnwindEx loop described here; our runs fail at frame validation instead. It may be the same root cause surfacing differently, or a neighbouring one — we cannot tell. Two observations that may help ------------------------------ 1. Raising the Unix stack limit measurably reduces the failure rate. Launching Wine through sh -c 'ulimit -s 65520; exec wine ...' instead of inheriting macOS's default 8 MB removes most of the stack overflow messages and improves the launch rate. Measured on cold starts, same prefix, same binary, alternating conditions: default stack limit 17 attempts for 5 launches 64 MB stack limit 7 attempts for 5 launches That a larger *host* stack helps, when the overflowing amounts are only a few hundred bytes, fits comment 1: the failure is not that the application needs more stack, it is that the exception path has no headroom left to run. Raising the limit does not fix the cause, it makes the recovery path survivable. 2. The reported fault address does not match the reported instruction. Our application writes a minidump. It records C0000005 at 7BF21239 with a faulting read address of 0x00002dc1. With WINEDEBUG=+loaddll, 7BF21239 falls inside wow64cpu.dll (loaded at 7BF20000), in the function that ends where BTCpuSimulate begins — the syscall entry thunk, just after xchgq %r14, %rsp. The instruction at that offset is a RIP-relative load: 7BF21239: movl 0x2dc1(%rip), %edx Executed at 0x7bf21239, its effective address should be around 0x7bf24000, not 0x2dc1 — which is the instruction's own displacement field. The exception record's memory address is therefore inconsistent with the effective address implied by the reported instruction pointer and instruction bytes, suggesting the state propagated through the WoW64 exception path does not faithfully describe the 64-bit thunk. Similarly, in call_seh_handlers above, 0x0022B8C0 looks like a 32-bit guest stack address while 0x100112000–0x10020FD20 is plainly a 64-bit range: a guest frame appears to be validated against host stack bounds. Ruled out on our side --------------------- Measured with alternating conditions, three launches per series, because the failure rate is widely dispersed (the unmodified configuration ranged from 3 to 12 attempts per three launches): raising the PE header stack reserve to 8 MB, restricting the process to efficiency cores, WINEPRELOADRESERVE, dbghelp=disabled, DXVK for D3D9, clearing the application's shader cache, windowed instead of fullscreen, and verbose WINEDEBUG channels. One more thing that helps, for whatever it is worth: running a trivial 32-bit cmd.exe before the application more than halves the failure rate on a cold prefix. That points at per-process or per-thread WoW64 setup rather than at anything the application does. We can attach the minidump, the module map and full stderr if useful. We cannot provide a reproducible test case — the application is a private server client — but nothing about it looks special: it is an ordinary 2013 Direct3D 9 title that raises a lot of exceptions during initialisation. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60281 --- Comment #3 from Victor <victormorel.pro@gmail.com> --- Following up on the register state above, with what we think the possible fix directions are — and a question for whoever knows this code. What we think happens --------------------- An exception is taken while the thread is somewhere around the stack switch in syscall_32to64. At that moment the host stack is still parked in R14 and RSP holds the guest stack. call_seh_handlers on the 64-bit side then unwinds from that context, and is_valid_frame measures the resulting establisher frame against NtCurrentTeb()->Tib.StackLimit/StackBase, which are the host bounds. The frame cannot pass, the exception is not dispatched, and the process dies on what is usually a longjmp. Directions we can see --------------------- 1. Give the thunk real unwind information, so the unwinder stops at it. If RtlVirtualUnwind treated syscall_32to64 as a boundary rather than walking through it into the guest stack, the validity check would never be reached with a guest frame. This looks like the cleanest of the three, and it is in the same spirit as the existing special-casing of BTCpuSimulate for relay. 2. Repair the context before dispatching. Recognise a RIP inside the thunk's range and reconstruct the intended state — put RSP and R14 back the way they belong — before letting dispatch proceed. More surgical, but it needs the thunk's boundaries to be known to the exception path, and it has to distinguish "before the swap" from "after". 3. Select the guest stack bounds when the exception came from 32-bit mode. We would argue against this one. It would silence the message, but the unwinder would then continue on a state we already know to be wrong, trading a loud failure for a quiet one. There is also the headroom question from comment 1, which we think is a second, partly independent facet: even with the bounds sorted out, an exception taken when the stack is nearly full still has to be recoverable. Our ulimit workaround only buys room, it does not address that. What we can offer ----------------- We have not built Wine yet — the report above comes from reading the source alongside our traces. We are willing to build an instrumented tree and collect whatever is useful: RSP, R14, R13, RIP around the xchgq, how often an exception actually lands inside that window, and whether the swap is genuinely mid-flight or already corrupt on entry. We also have a measurement protocol that survives the noise: this failure rate is dispersed enough that a single series proves nothing — our unmodified configuration ranged from 3 to 12 attempts per three launches — so we alternate conditions and use ten launches or more per arm. We can run that against a patched build. The question, then: is direction 1 the one worth trying, or is there a reason the thunk deliberately has no unwind information that we are not seeing? We would rather ask than send a patch that misses the point. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=60281 --- Comment #4 from ssnffb30 <ssnffb30@gmail.com> --- This might be an issue related to Rosetta 2. You might want to check out bug 60034. In my case, patching wow64cpu.dll allowed the UltraViewer installer to work properly. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
participants (1)
-
WineHQ Bugzilla