You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[wowbox64] WoW 3.3.5a (32-bit WOW64 guest) crashes or freezes at character list — EmuRun host-pointer corruption and recursive exception-delivery guard-page loop #4380
Running a 32-bit Windows guest (World of Warcraft 3.3.5a) under Hangover
WowBox64 on ARM64 Android fails at the character list in two different ways,
while the same prefix works under libwow64fex.dll (FEX) and under the legacy
x86-64-on-box64 path:
Crash form: the host register x19, which holds the C local emu
pointer inside EmuRun (src/dynarec/dynarec.c), is corrupted to 0x14
during a JIT excursion. The next use, ldr w1, [x19, #0x548]
(emu->fork), reads address 0x55C and the guest reports ERROR Map bricks below first 2GB of address space #132
(0xC0000005, read at 0x55C).
Freeze form: Wine's 32-bit exception delivery
(wow64.dll!call_user_exception_dispatcher) repeatedly builds a WOW64_CONTEXT delivery frame on the guest stack. Each delivery consumes 0x340 bytes; after enough recursive deliveries the frame write lands in a PROT_NONE guard page below the thread stack. The faulting store loops
forever at 100% CPU.
Both failures begin immediately after the client requests the character list
(COP_GET_CHARACTERS).
Environment
Device: Xiaomi Pad 6S Pro 12.4 (24018RPACC), Snapdragon 8 Gen 2,
Adreno 740, Android 16, aarch64
Guest: World of Warcraft 3.3.5a build 12340 (32-bit, Wow.exe)
Translator: default HODLL=wowbox64.dll; CPU affinity 4-7
Box64 source: tag v0.4.4, commit 2f130fab1d6e1a4ee8a71dc60cfdfcc839ad192a
Reproduction
Start the 32-bit client with WowBox64/Hangover.
Log in (auth succeeds: LOGIN_STATE_AUTHENTICATED, AUTH_OK).
Wait for the character list. The failure appears within ~0.1-1 s of COP_GET_CHARACTERS.
With libwow64fex.dll the same character list loads. The legacy
box64/Wine path also passes. On Windows the client does not crash here.
Crash form evidence
Crash text (one of several runs, all equivalent):
ERROR #132 (0x85100084) Fatal Exception
Exception: 0xC0000005 (ACCESS_VIOLATION) at 0023:7A953828
The instruction at "0x7A953828" referenced memory at "0x0000055C".
EAX=00401000 EBX=024DF9EC ECX=00000000 EDX=00000000 ESI=EDI=00002340
EBP=024DFA04 ESP=024DF98C EIP=7BDEBC20 FLG=00000206
CS=0023 DS=002B ES=002B SS=002B FS=0053 GS=002B
0x7A953828 is inside the relocated image of wowbox64.dll. Symbol lookup
(COFF symbols in the installed DLL) and disassembly of the live process
resolve it to EmuRun, immediately after a JIT entry call:
4e3a10: mov x0, x194e3a14: bl arm64_prolog ; enter JIT (x0=emu, x1=jblock)4e3a18: mov w26, wzr4e3a1c: ldr w1,[x19, #0x548] ; emu->fork; faults when x19 is corrupt4e3a20: cbnz w1, <fork-handling>
At the fault x19 = 0x14, so [x19 + 0x548] = 0x55C. The same 0x55C
signature appears at several offsets inside EmuRun across runs
(0x7A953828, 0x7A953858, 0x7A953948, 0x7A953A1C; the artifact layout
differs between builds, so absolute addresses are not directly comparable).
An earlier crash at 0x7A960124 occurred in the same relocated module region
but the minidump for that run did not record exception parameters.
arm64_prolog/arm64_epilog save and restore the host callee-saved
registers (x19..x28, d8..d15) on a stack frame addressed by x28. The C
code assumes x19 is preserved across the JIT excursion.
A diagnostic build that logged the first WOW64 context transition produced
this event immediately before the fatal state:
FindDynablockFromNativeAddress() returned 0 for the faulting PC even though
it lies inside the anonymous executable region that holds the relocated wowbox64.dll text (the image is mapped in the 32-bit address range in this
configuration).
Freeze form evidence
Live GDB capture of the spinning main thread (100% CPU, 34 threads; all
other threads waiting on futex/poll/select/pipe):
Esp = 0x01CE11CC is frame_base + 0x340, i.e. the frame is built exactly 0x340 bytes below the guest ESP. The guest stack is a 9.2 MB region whose
bottom is 0x01CE1000; after repeated deliveries the next frame base falls
into the guard page (0x01CE0E8C), the store faults, and the dispatcher
re-enters itself forever. Multiple identical delivery frames were present on
the stack at 0x1CE11CC, 0x1CE324C, etc.
Live breakpoint tracing (8/8 identical hits) shows the host side of the same
freeze spinning in Wine's own unwinder: virtual_unwind calls RtlLookupFunctionEntry for ControlPc 0x106BEB520 — an address inside the
anonymous rwxp mapping 106770000-106d70000, i.e. JIT-generated code with
no unwind data — gets NULL back, and retries the same frame forever (each
round trips the loader lock: RtlEnterCriticalSection / RtlLeaveCriticalSection, ~100% of one core). So the freeze is a
no-progress unwind/dispatch cycle: the unwinder cannot step past a JIT frame,
and the 32-bit dispatcher cannot write its frame. Box64 v0.4.4 sources
contain no dynamic unwind-table registration (RtlInstallFunctionTableCallback
/ RtlAddFunctionTable / .pdata emission for JIT blocks), although the API
exists in the installed ntdll.dll.
Related observation (may be a separate bug): src/os/os_wine.c implements
void*GetSegmentBase(void*emu, uint32_tdesc)
{
printf_log(LOG_NONE, "GetSegmentBase does not apply to Wine dlls\n");
returnNULL;
}
GetSegmentBaseEmu() stores that NULL into emu->segs_offs[seg]. Both the
interpreter (x64run.c) and generated dynarec code read segs_offs[] at
runtime, so an interpreted instruction carrying a segment override (fs:,
selector 0x53) zeroes the FS base for subsequent accesses until the next BTCpuSimulate recomputes it. The freeze's original fault — a read of
absolute 0x18 (fs:[0x18] is the x86 TEB self pointer) from the
interpreter — is consistent with a lost FS base. A candidate patch returning calculate_fs() for selector 0x53 was built and tested: it removed the GetSegmentBase log flood and startup worked, but the character-list crash
signature was unchanged, so it is not a fix for the crash form and its effect
on the freeze form could not be isolated (that run crashed instead of
freezing). Flagging it as a suspicious implementation gap rather than a
confirmed cause.
BOX64_DYNAREC_CALLRET=2: unsupported for WowBox64 per docs; process
became a zombie; not pursued.
BOX64_DYNAREC_CALLRET=0: game exits silently during startup (2.6 KB log,
ends after DXVK init, no crash files); untestable for the character list.
BOX64_DYNAREC_STRONGMEM=1 + WEAKBARRIER=0: user-observed freeze at the
character list persists (strictest memory ordering changes nothing).
BOX64_DYNACACHE=0: identical silent startup exit as CALLRET=0 (2,662-byte
log, ends after DXVK init, no crash files); untestable for the character
list. Both translation-machinery flags break startup outright, leaving the
character-list defect itself untested under them.
BOX64_NOSIGSEGV=1: identical silent startup exit again (2,662-byte log,
same cutoff point, no crash files). Three independent deviations from the
default translation/signal configuration all prevent startup entirely,
which points to unmaintained non-default paths in this WowBox64 build
rather than anything game-specific.
BOX64_DYNAREC_INTERP_SIGNAL=1: starts normally (unlike the three above)
and freezes at the character list with the identical signature — faulting
PC 0x7fff85c1d8 (wow64.dll store), frame base x24 = 0x1ce0e6c inside
the same PROT_NONE guard page. Interpreter-based signal handling changes
neither the failure mode nor its location.
M2UseThreads=0 in the game config: no change.
FEX via --hodll libwow64fex.dll: passes the character list (in-world
performance is worse for this user, but that is a performance issue, not
this crash).
Legacy x86-64-on-box64 Wine lane: passes.
Interpreter-only trial (BOX64_DYNAREC=0): inconclusive; did not reach a
usable window in the allotted time.
FS-base patch described above: no change to the crash form.
Questions
Does arm64_prolog/arm64_epilog guarantee restoration of all callee
saved host registers (x19..x28, d8..d15) on every return path,
including blocks that exit to C through callbacks and blocks that end
after a guest exception? Is there a known issue with secondary entry
points (block->sep[]) in this respect?
In BTCpuResetToConsistentState, when ctx->Pc is not recognized as JIT
code (FindDynablockFromNativeAddress() returns 0), the context is
forwarded unchanged. Could a guest exception raised inside the emulator
(with ctx->Pc inside wowbox64's own image) leave emu/x19 holding
guest register values when control returns to EmuRun?
Which supported diagnostic would you recommend to trace JIT entry/exit
and the segs_offs[] lifecycle for a 32-bit Wine guest?
For the freeze form: how should 32-bit exception delivery behave when the
guest stack is at its guard page? Is the expectation that Wine grows the
stack, or should the delivery avoid re-entering call_user_exception_dispatcher from an identical context?
Should WowBox64 register its JIT code with the host unwinder (e.g. via RtlInstallFunctionTableCallback, which exists in this ntdll.dll), or
is there an existing mechanism that is supposed to keep virtual_unwind
from looping on JIT PCs that have no unwind data?
Attachments
Available privately on request (sanitized to remove session/account data):
Crash.txt + Crash.dmp from the crash form (several runs)
Crash.txt + Crash.dmp from the freeze-form crash
Full GDB captures of the freeze loop and the EmuRun fault
Custom JSONL trace of WOW64 entry/exception events
Summary
Running a 32-bit Windows guest (World of Warcraft 3.3.5a) under Hangover
WowBox64 on ARM64 Android fails at the character list in two different ways,
while the same prefix works under
libwow64fex.dll(FEX) and under the legacyx86-64-on-box64 path:
x19, which holds the C localemupointer inside
EmuRun(src/dynarec/dynarec.c), is corrupted to0x14during a JIT excursion. The next use,
ldr w1, [x19, #0x548](
emu->fork), reads address0x55Cand the guest reports ERROR Map bricks below first 2GB of address space #132(
0xC0000005, read at0x55C).(
wow64.dll!call_user_exception_dispatcher) repeatedly builds aWOW64_CONTEXTdelivery frame on the guest stack. Each delivery consumes0x340bytes; after enough recursive deliveries the frame write lands in aPROT_NONEguard page below the thread stack. The faulting store loopsforever at 100% CPU.
Both failures begin immediately after the client requests the character list
(
COP_GET_CHARACTERS).Environment
24018RPACC), Snapdragon 8 Gen 2,Adreno 740, Android 16, aarch64
hangover-wine 11.16andhangover-wowbox64 11.16(WowBox64 arm64 v0.4.4)Wow.exe)HODLL=wowbox64.dll; CPU affinity 4-7v0.4.4, commit2f130fab1d6e1a4ee8a71dc60cfdfcc839ad192aReproduction
LOGIN_STATE_AUTHENTICATED,AUTH_OK).COP_GET_CHARACTERS.With
libwow64fex.dllthe same character list loads. The legacybox64/Wine path also passes. On Windows the client does not crash here.
Crash form evidence
Crash text (one of several runs, all equivalent):
0x7A953828is inside the relocated image ofwowbox64.dll. Symbol lookup(COFF symbols in the installed DLL) and disassembly of the live process
resolve it to
EmuRun, immediately after a JIT entry call:At the fault
x19 = 0x14, so[x19 + 0x548] = 0x55C. The same0x55Csignature appears at several offsets inside
EmuRunacross runs(
0x7A953828,0x7A953858,0x7A953948,0x7A953A1C; the artifact layoutdiffers between builds, so absolute addresses are not directly comparable).
An earlier crash at
0x7A960124occurred in the same relocated module regionbut the minidump for that run did not record exception parameters.
arm64_prolog/arm64_epilogsave and restore the host callee-savedregisters (
x19..x28,d8..d15) on a stack frame addressed byx28. The Ccode assumes
x19is preserved across the JIT excursion.A diagnostic build that logged the first WOW64 context transition produced
this event immediately before the fatal state:
FindDynablockFromNativeAddress()returned 0 for the faulting PC even thoughit lies inside the anonymous executable region that holds the relocated
wowbox64.dlltext (the image is mapped in the 32-bit address range in thisconfiguration).
Freeze form evidence
Live GDB capture of the spinning main thread (100% CPU, 34 threads; all
other threads waiting on futex/poll/select/pipe):
Maps at the fault:
The source structure passed to the frame copy was decoded as a 32-bit
exception record:
The
WOW64_CONTEXTbeing delivered contained:Esp = 0x01CE11CCisframe_base + 0x340, i.e. the frame is built exactly0x340bytes below the guest ESP. The guest stack is a 9.2 MB region whosebottom is
0x01CE1000; after repeated deliveries the next frame base fallsinto the guard page (
0x01CE0E8C), the store faults, and the dispatcherre-enters itself forever. Multiple identical delivery frames were present on
the stack at
0x1CE11CC,0x1CE324C, etc.Live breakpoint tracing (8/8 identical hits) shows the host side of the same
freeze spinning in Wine's own unwinder:
virtual_unwindcallsRtlLookupFunctionEntryfor ControlPc0x106BEB520— an address inside theanonymous
rwxpmapping106770000-106d70000, i.e. JIT-generated code withno unwind data — gets
NULLback, and retries the same frame forever (eachround trips the loader lock:
RtlEnterCriticalSection/RtlLeaveCriticalSection, ~100% of one core). So the freeze is ano-progress unwind/dispatch cycle: the unwinder cannot step past a JIT frame,
and the 32-bit dispatcher cannot write its frame. Box64
v0.4.4sourcescontain no dynamic unwind-table registration (
RtlInstallFunctionTableCallback/
RtlAddFunctionTable/.pdataemission for JIT blocks), although the APIexists in the installed
ntdll.dll.Related observation (may be a separate bug):
src/os/os_wine.cimplementsGetSegmentBaseEmu()stores thatNULLintoemu->segs_offs[seg]. Both theinterpreter (
x64run.c) and generated dynarec code readsegs_offs[]atruntime, so an interpreted instruction carrying a segment override (
fs:,selector
0x53) zeroes the FS base for subsequent accesses until the nextBTCpuSimulaterecomputes it. The freeze's original fault — a read ofabsolute
0x18(fs:[0x18]is the x86 TEB self pointer) from theinterpreter — is consistent with a lost FS base. A candidate patch returning
calculate_fs()for selector0x53was built and tested: it removed theGetSegmentBaselog flood and startup worked, but the character-list crashsignature was unchanged, so it is not a fix for the crash form and its effect
on the freeze form could not be isolated (that run crashed instead of
freezing). Flagging it as a suspicious implementation gap rather than a
confirmed cause.
Experiments performed
BOX64_DYNAREC_BIGBLOCK=0: freeze persists; run log byte-identical(616,348 bytes, 10,075 segment-base messages).
BOX64_DYNAREC_LOG=2(workaround mentioned in issue [WoWBox64] Freeze Issue, but works with BOX64_DYNAREC_LOG=2 #3018): no effect;produced a ~914 MB log.
BOX64_DYNAREC_CALLRET=2: unsupported for WowBox64 per docs; processbecame a zombie; not pursued.
BOX64_DYNAREC_CALLRET=0: game exits silently during startup (2.6 KB log,ends after DXVK init, no crash files); untestable for the character list.
BOX64_DYNAREC_STRONGMEM=1+WEAKBARRIER=0: user-observed freeze at thecharacter list persists (strictest memory ordering changes nothing).
BOX64_DYNACACHE=0: identical silent startup exit asCALLRET=0(2,662-bytelog, ends after DXVK init, no crash files); untestable for the character
list. Both translation-machinery flags break startup outright, leaving the
character-list defect itself untested under them.
BOX64_NOSIGSEGV=1: identical silent startup exit again (2,662-byte log,same cutoff point, no crash files). Three independent deviations from the
default translation/signal configuration all prevent startup entirely,
which points to unmaintained non-default paths in this WowBox64 build
rather than anything game-specific.
BOX64_DYNAREC_INTERP_SIGNAL=1: starts normally (unlike the three above)and freezes at the character list with the identical signature — faulting
PC
0x7fff85c1d8(wow64.dllstore), frame basex24 = 0x1ce0e6cinsidethe same
PROT_NONEguard page. Interpreter-based signal handling changesneither the failure mode nor its location.
M2UseThreads=0in the game config: no change.--hodll libwow64fex.dll: passes the character list (in-worldperformance is worse for this user, but that is a performance issue, not
this crash).
BOX64_DYNAREC=0): inconclusive; did not reach ausable window in the allotted time.
Questions
arm64_prolog/arm64_epilogguarantee restoration of all calleesaved host registers (
x19..x28,d8..d15) on every return path,including blocks that exit to C through callbacks and blocks that end
after a guest exception? Is there a known issue with secondary entry
points (
block->sep[]) in this respect?BTCpuResetToConsistentState, whenctx->Pcis not recognized as JITcode (
FindDynablockFromNativeAddress()returns 0), the context isforwarded unchanged. Could a guest exception raised inside the emulator
(with
ctx->Pcinside wowbox64's own image) leaveemu/x19holdingguest register values when control returns to
EmuRun?and the
segs_offs[]lifecycle for a 32-bit Wine guest?guest stack is at its guard page? Is the expectation that Wine grows the
stack, or should the delivery avoid re-entering
call_user_exception_dispatcherfrom an identical context?RtlInstallFunctionTableCallback, which exists in thisntdll.dll), oris there an existing mechanism that is supposed to keep
virtual_unwindfrom looping on JIT PCs that have no unwind data?
Attachments
Available privately on request (sanitized to remove session/account data):
Crash.txt+Crash.dmpfrom the crash form (several runs)Crash.txt+Crash.dmpfrom the freeze-form crashEmuRunfault58d036888d638f05a72193702f97071aabc9baf7959e7736d48d9c450acc3440ac9dab62c5494f6bd7a199f5849d394f6a33c39ba808062fa8ad53b2a6e935afaafcf6adc9da3a6af0c5eb945f86f311df400a8479ac546bf5e3ad34901d56810cf22d12e66ef3f8d7e1d651b6f0a39c23a521c70227b0aa928b294e8675c434The raw dumps contain user/session data and are not posted publicly.