29 KiB
Debug Load Workflow
Use this when comparing:
- one successful manual load of
hh - one failing hook-driven auto-load attempt
The goal is to compare the real successful owner path above 0x00445ac0 against the failing hook-driven path.
Current Findings
From the current logs:
- successful manual load now has a grounded pre-call site at
0x004390cbwith:ECX = 0x02af5840[0x006cec78] = 0x02af5840[0x006cec74] = 0x01d81230- top-of-stack dwords:
arg1 = 0x01db4739arg2 = 4arg3 = 0x0022fb50- next dword =
0x026d7b88
- the subsequent successful
0x00445ac0entry still has:ret = 0x004390d0arg1 = 0x01db4739arg2 = 4arg3 = 0x0022fb50
- older failing auto-load attempts never reached
0x00445ac0 - the earlier failing breakpoint was
0x00517cf0with:[0x006cec78] = 0[0x006cec74] = 0x01d81230
- the staged request globals at
0x006ce9b8..0x006ce9c4and0x006d1270..0x006d127care zero on the successful manual path
That older 0x00517cf0 result is no longer the current blocker. The hook now reaches the real coordinator entry, so the remaining gap is later shell timing or re-entrancy, not request-latch shape.
The disassembly at 0x004390b0..0x004390cb is now the strongest grounded manual-load branch:
- it writes
[0x006cec74+0x6c] = 1 - it computes
arg1from([0x006cec7c] + 0x11) - it pushes
arg2 = 4 - it passes
arg3 = &out_success - and then calls
0x00445ac0
So any hook experiment that does not reproduce that exact shape is no longer a plausible match for the successful manual path.
Latest Auto-Load Comparison
The newest hook-driven debugger run now reaches 0x00445ac0 directly.
At the auto-load 0x00445ac0 breakpoint:
- stack:
ret = 0x7650505cinsidedinput8arg1 = 0x01db4739arg2 = 4arg3 = 0x0022fcf8
- globals:
[0x006cec74] = 0x01d81230[0x006cec7c] = 0x01db4728[0x006cec78] = 0x026d7b88
Compared to the successful manual path:
arg1matches exactly:0x01db4739arg2matches exactly:4[0x006cec74]matches exactly:0x01d81230[0x006cec7c]still matches the same runtime-profile base used to derivearg1[0x006cec78]is now non-null and published before entry
So the hook is no longer missing the coordinator entry shape. The remaining question is no longer "can we reach 0x00445ac0?" but "does the live non-debugger call return successfully and trigger the actual restore transition?"
Latest Plain-Run Narrowing
The current non-debugger auto-load path no longer looks like the original shell-side crash at
0x0053fea6.
The hook-side state machine is now stable up to the handoff into shell_transition_mode:
rrt-hook: auto load shell transition enteringrrt-hook: auto load shell unpublish enteringrrt-hook: auto load shell unpublish entry this=0x029b3a08 object=0x026d7b88
So the old hook-side gating and bad-call-shape problems are no longer the blocker.
The current runtime probes now push the remaining stall much later than the original old-mode
teardown inside shell_transition_mode:
shell_transition_modeenters- old shell-window unpublish at
0x005389c0enters with:- shell bundle
this = 0x029b3a08 - old object
object = 0x026d7b88
- shell bundle
- the inner wrapper
0x005400c0(object)returns - the full
0x53fe00 -> 0x53f860remove-node sweep over[object+0x74]returns and clears[object+0x70/+0x74] shell_unpublishitself then returns cleanly- the nearby mode-
2teardown helper0x00502720returns shell_load_screen_window_construct0x004ea620returns- the immediate shell publish through
0x00538e50returns shell_transition_modeitself returns cleanly
At the same time, one later load-side probe still does not fire:
- no
shell_active_mode_run_profile_startup_and_load_dispatch0x00438890entry
So the current live stall is now best read as:
- after the old-object unpublish path at
0x005389c0 - after the inner
0x5400c0 -> 0x53fe00 -> 0x53f860teardown sweep - after the nearby mode-
2teardown helper0x00502720 - after the mode-
4LoadScreen.winconstructor and immediate shell publish - but still before any trusted runtime evidence that
0x00438890has entered
The richer plain-run snapshots now tighten the old-object state too:
- the old object is still the expected
Setup.wininstance with vtable0x005d1664 - the shell bundle head and tail both point to that same object
[object+0x54]and[object+0x58]are both null, so the outer unlink state is consistent[object+0x74]is non-null and the first two linked nodes recovered from+0x8aalso look structurally sane:- first node
0x02a74470: vtable0x005dd870, type0xea72, owner-ish field0x02a067b8, next0x02a04b38 - second node
0x02a04b38: vtable0x005dd870, type0xea71, owner-ish field0x02a067b8, next0x02a03e38
- first node
So the remaining leading hypothesis is no longer "the list head is already garbage." The later
shared node vcall target 0x540910 is healthy in general and does not fire on the failing
transition path. The newer direct probes narrow it even further: the failing transition still does
not reach 0x53fe00 or 0x53f860. That pushes the current boundary into the tiny wrapper layer
between shell_unpublish entry and the 0x53fe00 call, with 0x5400c0(object) now the next
useful direct probe.
The latest plain Wine log also ends with a matching crash:
wine: Unhandled page fault on read access to 02E11000 at address 02E11000
Static disassembly sharpened the remaining boundary one step further, but the newer jump-table decode changes the interpretation materially. The startup-runtime slice
0x004ea7100x0053b070(0x46c40)0x004336d00x00438890
is not owned by mode 4. It is owned by jump-table entry 1 at 0x483012. Jump-table entry 4
lands at 0x4832e5 instead and only constructs and publishes a plain LoadScreen.win object
through 0x004ea620 and 0x00538e50.
So the next useful probe is no longer the mode-4 branch’s pre-dispatch runtime-object helper,
because mode 4 does not own that startup-runtime path at all. The next useful test is the real
startup-dispatch entrypoint: shell_transition_mode(1, 0).
The latest plain runs tightened that correction one more step:
- the direct
0x004336d0runtime-reset probe still does not fire - the direct
0x00438890startup-dispatch probe still does not fire - but
shell_transition_mode,LoadScreen.winconstruction, and the immediate shell publish all still return cleanly
That no longer means the post-construct startup slice is mysteriously skipped inside mode 4.
Instead, it matches the corrected static decode exactly: the hook has been entering the plain
load-screen branch rather than the startup-runtime branch.
The next best runtime target is therefore no longer another allocator cut under mode 4. It is a
direct test of shell_transition_mode(1, 0), which is the jump-table arm that statically owns the
startup-runtime allocation and 0x00438890 dispatch.
Current Pause Point
Current recorded stop point:
- the old hook-side crash and teardown corruption are resolved
- the static jump-table decode at
0x48342cshows the hook had been entering the wrong arm shell_transition_mode(4, 0)is only the plainLoadScreen.winbranchshell_transition_mode(1, 0)is the startup-dispatch branch that owns:0x004ea7100x0053b070(0x46c40)0x004336d00x00438890
So the next live experiment, when this work resumes, should start from the corrected mode-1
transition path rather than adding more probes under mode 4.
Two corrective notes from the allocator probe passes:
- the first allocator experiment at
0x005a125dwas not trustworthy, because that shared cdecl body sits behind the0x0053b070thunk and the initial hook used the wrong entry shape and split its first internalcall - the first direct thunk hook on
0x0053b070was also not trustworthy as implemented, because a copied relative-jmpthunk cannot be replayed through an ordinary trampoline
The next trustworthy allocator boundary is still the exact mode-4-branch thunk at 0x0053b070,
but only with a detour that calls the original target 0x005a125d directly instead of executing
the copied thunk bytes.
The latest filtered run exposed a more basic gating issue too: the log only reached one
gate mask 0x7 line with mode_id = 2, and it never advanced into ready gate passed, staging,
or transition. So that run did not actually exercise the load-screen startup subchain; it mostly
recorded ordinary shell-node activity plus one late ready-state observation. The old default gate
of 30 ready polls plus 5 deferred polls was therefore too conservative for this workflow. The
next run now lowers those defaults to 1 and 0, and adds an explicit ready-count log so the
trace should either stage immediately or show exactly how far the gate gets.
That gate adjustment worked on the next run: the hook now reaches ready count, stages selector
3, enters shell_transition_mode, returns from the LoadScreen.win construct and publish
helpers, and reports success again. But the allocator side is still unresolved:
- there is still no trusted
0x46c40allocator hit from0x0053b070 - there is still no direct
0x004336d0runtime-reset entry - there is still no direct
0x00438890startup-dispatch entry
So the next clean post-publish boundary is the tiny scalar setter at 0x004ea710, which is the
last straightforward callsite in the static mode-4 branch immediately before the 0x0053b070
allocation.
The immediate next runtime check is even more concrete than that helper hook, though: inspect the
state that 0x004ea710 should leave behind. Right after shell_transition_mode returns, the hook
now logs:
0x006d10b0(LoadScreen.winsingleton)[LoadScreen.win+0x78]0x006cec78[0x006cec74+0x0c][0x006cec7c+0x01]
If 0x004ea710 really ran on the mode-4 branch, [LoadScreen.win+0x78] should no longer be
zero after transition return.
The latest run answered that question directly:
shell_transition_modestill returns cleanlyfield_active_mode_objectis still theLoadScreen.winsingleton0x006cec78is still null[LoadScreen.win+0x78]is still0- startup selector remains
3
So the strongest current read is no longer “the helper hooks might be missing a straight-line call.”
At transition return, RT3 still looks like it is parked in the plain LoadScreen.win state rather
than having entered the separate runtime-object path at all. The next useful runtime cut is
therefore not deeper inside shell_transition_mode, but on the later active-mode service cadence:
does a subsequent service tick on the LoadScreen.win object populate [+0x78] or promote
0x006cec78 into the startup-dispatch object on a later frame?
The next run now logs the first few shell-state service ticks after auto-load is attempted with the same state tuple:
0x006cec78[0x006cec74+0x0c]0x006d10b0[LoadScreen.win+0x78]- startup selector
So the next question is very narrow: does that tuple stay frozen in the plain LoadScreen.win
shape, or does one later service tick finally promote it into the startup-runtime object path?
The latest service-tick run makes that boundary stronger still:
- the first later shell-state service ticks
count=2..8all keep the same frozen state 0x006cec78stays0[shell_state+0x0c]stays theLoadScreen.winsingleton[LoadScreen.win+0x78]stays0
So the active-mode service pass itself is not promoting the plain load screen into the startup
runtime object during those first later frames. The next best runtime boundary is now the
LoadScreen.win message owner 0x004e3a80, because that is the remaining live owner most likely
to receive the trigger that seeds page id [this+0x78], allocates the 0x46c40 startup runtime,
and later publishes 0x006cec78.
One later run did not reach that boundary at all:
- the new
0x004e3a80hook installed successfully - but there were no
ready count, staging, transition, post-transition, or load-screen-message lines anywhere in the log - the trace only showed ordinary shell node-vcall traffic before the window was closed
So that run is best treated as "auto-load path not exercised", not as evidence that the
LoadScreen.win message owner stayed silent after a successful transition. The next useful runtime
check is therefore one step earlier again: add a small first-few-calls trace on
shell_state_service_active_mode_frame itself so we can confirm whether that detour is firing on
the run at all and what mode id and gate mask it sees before the auto-load gate would stage.
That newer service-entry trace now confirms the full cadence:
- the service detour is firing
- the gate does stage and transition on counts
1 -> 2 - the transition returns cleanly
- later service ticks run with
mode_id = 4
At the same time, the next two probes are now bounded as negative results on that successful path:
- the
LoadScreen.winmessage hook at0x004e3a80stayed completely silent - the plain post-transition state still stays frozen with:
0x006cec78 = 0field_active_mode_object = LoadScreen.win[LoadScreen.win+0x78] = 0
So the next best boundary is no longer the message owner itself. It is the shell-runtime prime call
at 0x00538b60, because 0x00482160 still takes that branch on the null-0x006cec78 service
path before the later frame-cycle owner 0x00520620.
The first 0x00538b60 probe run is not trustworthy yet, though:
- the hook installed
- but the log stopped immediately after the first
shell-state service entry count=1 ... gate_mask=0x7 mode_id=2 ... - there were no ready-count lines, no transition lines, and no runtime-prime entry lines
So that result currently reads as "the new runtime-prime instrumentation likely interrupted the
first service pass" rather than as a real RT3 boundary shift. The next corrective step is to log
the matching shell-state service return and to trace the first few 0x00538b60 calls even before
AUTO_LOAD_ATTEMPTED becomes true. That will tell us whether the first service pass actually
returns and whether the runtime-prime hook is firing at all.
The static branch under 0x00482160 also adds one more caution: 0x00538b60 is conditional, not
unconditional. The service pass only enters it when the shell runtime at 0x006d401c is live and
[shell_state+0xa0] == 0. So a silent 0x00538b60 probe does not yet prove the shell is frozen
before the runtime-prime call; it may simply mean the +0xa0 gate stayed nonzero on that service
tick. The next service-entry logs therefore need to include [shell_state+0xa0] before we treat
runtime-prime silence as meaningful.
The newer run closes that conditional question:
[shell_state+0xa0]is0on the first traced service call0x00538b60is therefore eligible- the runtime-prime probe now shows it entering and returning cleanly on that same service tick
The later run closes the next owner too:
0x00520620shell_service_frame_cyclealso enters and returns cleanly on the same frozen mode-4path- the logged state matches the generic frame-service branch:
[+0x1c] = 0[+0x28] = 0flag_56 = 0[+0x58]is pulsed and then cleared back to00x006cec78stays0
The newer run closes that owner too:
0x0053fda0enters and returns cleanly on the frozen mode-4path- it is actively servicing the
LoadScreen.winobject itself - the serviced object keeps
field_1d = 1,field_5c = 1, and a stable child list - the first child vcall target at
+0x18stays0x005595d0 0x006cec78still stays0
So the next live boundary is now the child-service target itself at 0x005595d0, not the higher
object walker.
The child-service run narrows that again. The first sixteen 0x005595d0 calls under the serviced
LoadScreen.win object are stable, presentation-heavy child lanes:
- every child points back to the same parent through
[child+0x86] = LoadScreen.win - the early children have
flag_68 = 0x03,flag_6a = 0x03, and return4 - the later siblings have
flag_68 = 0x00,flag_6a = 0x03, and return0 field_b0stays00x006cec78still stays0
Static disassembly matches that read: 0x005595d0 is gated by 0x00558670 and then spends most
of its body in draw or overlay helpers like 0x54f710, 0x54f9f0, 0x54fdd0, 0x53de00, and
0x552560. So this is a presentation-side child service path, not the missing startup-runtime
promotion.
That moved the next useful runtime target back to the transition-time allocator lane, but the
later jump-table decode changes what that means. The widened 0x0053b070 window below is now
best read as evidence for the plain mode-4 LoadScreen.win arm, not as evidence for the
startup-runtime arm.
The next widened allocator run immediately paid off, but in a narrower way than expected:
- the first traced transition-window allocation is
0x7c, which matches the static pre-construct0x48302a -> 0x53b070call exactly - the following
0x111,0x84,0x3a, and repeated0x25allocations all happen beforeLoadScreen.winconstruct returns, so they now read as constructor-side child or control setup - that means the allocator probe was not disproving the
0x46c40startup-runtime slice yet; it was simply exhausting its 16-entry log budget inside the constructor before the later post-construct block
The corrected follow-up run with that reset is now the decisive one: after LoadScreen.win
construct returns, there are still no further allocator hits before publish and transition return.
That matches the corrected jump-table decode cleanly, because mode 4 does not own the
0x46c40 -> 0x4336d0 -> 0x438890 path at all.
The first corrected thunk run also showed one practical problem: the probe became too noisy to be
useful as a boundary marker, because 0x0053b070 is used widely outside the load-screen path.
That still mattered, because it showed the hook-driven transition was taking the same 0x7c
constructor-side allocation as the plain mode-4 branch rather than the startup-runtime
allocation size 0x46c40.
Manual Owner Tail
The branch at 0x004390b0..0x004390ea now has a grounded post-call tail too:
0x004390cbcalls0x00445ac00x004390d0immediately calls0x004834e0(0, 1)on0x006cec74- if
out_success != 0oresi != 0,0x004390eacalls0x004384d0 - then
0x004390efcalls0x0053f310on0x00ccbb20 - then
0x00439104calls0x004834e0(0, 1)again
The successful manual breakpoint at 0x004390cb shows ESI = 0 and EDI = 1, so the manual load branch only forces the 0x004384d0 post-load pipeline when out_success comes back nonzero.
That makes the current hook gap narrower still: even with the correct 0x00445ac0 arguments, returning directly into dinput8 skips RT3's own owner-tail work unless we mirror it ourselves.
Owner Xrefs Above 0x438890
The containing owner at 0x00438890 is now grounded as a larger thiscall shell owner with two stack arguments. Current xrefs found in local disassembly are:
0x00443b570x00446d7f0x0046b8bc0x004830ca
The strongest caller so far is 0x004830ca:
- it publishes
0x006cec78 = eax - then calls
0x00438890asthiscall(active_mode, 1, 0) - it sits inside
shell_transition_mode - it is the branch that constructs
LoadScreen.winthrough0x004ea620 - and it continues through shell-window follow-up on
0x006d401cafter the0x00438890call
The surrounding mode map is tighter now too:
- mode
1=Game.win - mode
2=Setup.win - mode
3=Video.win - mode
4=LoadScreen.win - mode
5=Multiplayer.win - mode
6=Credits.win - mode
7=Campaign.win
That makes 0x00438890(active_mode, 1, 0) the strongest current RT3-native entry candidate for reproducing the successful manual load branch, because it owns the internal dispatch that later reaches 0x004390cb.
The containing shell-mode switcher ABI is tighter now too:
0x00482ec0is not a one-arg mode switch- it is a
thiscallwith two stack arguments - the grounded world-entry load-screen call shape at
0x443adf..0x443ae3is(4, 0) - the function confirms that shape itself by reading the requested mode from
[esp+0x0c]and returning withret 8 - the second stack argument is now best read as an old-active-mode teardown flag, because the
0x482fc6..0x482fffbranch only runs when it is nonzero and then releases the old active-mode object through0x00434300,0x00433730,0x0053b080, and finally clears0x006cec78
Current static xrefs also tighten the broader ownership split:
0x00443b57calls0x00438890from the world-entry side, but with(0, 0)after dismissing the current shell detail panel and servicing0x4834e0(0, 0)0x00446d7fcalls it from the saved-runtime restore side with the same(0, 0)shape before immediately building.smpbundle payloads through0x530c80/0x531150/0x5313600x0046b8bccalls it from the multiplayer preview family before a later0x00445ac0call0x004830cacalls it from the shell-side active-mode branch with the clearest(1, 0)setup
So the function is no longer just a guessed hook target. It is now a real shared owner above world-entry, saved-runtime restore, multiplayer preview, and shell-side active-mode startup branches.
The internal selector split inside 0x00438890 is tighter now too:
[0x006cec7c+0x01]is a startup-profile selector, not the shell mode id- selector values
1and7share the tutorial lane at0x00438f67, which writes[0x006cec74+0x6c] = 2and loadsTutorial_2.gmporTutorial_1.gmp - selector
2is a world-root initialization lane at0x00438fbethat allocates0x0062c120when needed, runs0x0044faf0, and then forces the selector to3 - selector
4is a setup-side world reset or regeneration lane at0x00439038that rebuilds0x0062c120from setup globals0x006d14cc/0x006d14d0, then runs0x00535100and0x0040b830 - selector values
3,5, and6collapse into the same profile-seeded file-load lane at0x004390b0..0x004390ea - selector
6is the one variant that explicitly writes[0x006cec74+0x6c] = 1before the shared file-load call
Current grounded writers now tighten those values too:
Campaign.winwrites selector6at0x004b8a2fMultiplayer.winwrites selector3on one pending-status branch at0x004f041e- the larger
Setup.windispatcher around0x005033d0..0x00503b7bwrites selectors2,3,4, and5on several validated launch branches - so the shared file-load lane is now best read as one reused profile-file startup family rather than one owner-specific manual-load path
That means the successful manual-load branch is not the whole function. It is one three-selector subfamily inside a broader startup dispatcher that also owns tutorial and fresh-world setup lanes.
The multiplayer preview side is also tighter now:
0x0046b8bcpublishes0x006cec78- calls
0x00438890asthiscall(active_mode, 0, 0) - clears
[0x006cec74+0x6c] - and only then calls
0x00445ac0(0x006ce630, [0x006ce9c0], 0)
That makes the preview relaunch path clearly different from the manual load branch, not just a differently staged copy of it.
Latest Headless Debugger Result
The scripted auto-load debugger run is now useful without manual interaction:
- all breakpoints were set successfully:
0x004388900x004390cb0x00445ac0
- older runs that also broke on
0x0053fea6stopped too early on that shell-side crash site - the default scripted compare flow now keeps only the owner-chain breakpoints above the real load lane
So the current non-interactive path is still good enough to gather repeatable crash-side state, but on this display setup the owner-chain compare flow is also vulnerable to early X11 death:
XF86VidModeClientNotLocal- process termination before the RT3 owner breakpoints fire
That means the current plain-run hook probes are more reliable than winedbg for narrowing the
live stall inside shell_transition_mode.
The latest static pivot also means the next reverse-engineering step does not require a live run:
- compare the mode-
4LoadScreen.winowner path at0x004830caagainst the world-entry and saved-runtime callers of0x00438890 - compare how the
(1, 0)LoadScreen.winlane diverges from the(0, 0)world-entry and saved-runtime lanes before control reaches the shared0x004390b0manual-load branch - only then return to hook experiments
Launchers
Manual debugger run:
tools/run_rt3_winedbg.sh
Auto-load debugger run:
tools/run_hook_auto_load_winedbg.sh hh
Both scripts use /opt/wine-stable/bin/winedbg explicitly, so they do not depend on winedbg being on PATH.
They also default to:
- their matching command file in tools/
- a logfile in the repo root:
To save the full interactive debugger session to a file, set RRT_WINEDBG_LOG:
RRT_WINEDBG_LOG=/tmp/rt3-manual-load-winedbg.log tools/run_rt3_winedbg.sh
or:
RRT_WINEDBG_LOG=/tmp/rt3-auto-load-winedbg.log tools/run_hook_auto_load_winedbg.sh hh
Those wrappers use script, so both the commands you type and the debugger output are captured.
winedbg under /opt/wine-stable also supports command files directly:
tools/run_rt3_winedbg.sh
and:
tools/run_hook_auto_load_winedbg.sh hh
Override either default if needed:
RRT_WINEDBG_LOG=/tmp/rt3-manual-load-winedbg.log tools/run_rt3_winedbg.sh
Ready-made debugger command files are also provided:
The default auto-load debugger run is now crash-first. It does not set RT3 owner breakpoints. Instead, it:
- continues immediately
- lets
winedbgstop on the first exception - dumps registers
- dumps the top four stack dwords
- prints a backtrace
Use that default when the hook is already known to stage and return from shell_transition_mode,
and the current question is the downstream crash site.
If you specifically want the earlier owner-chain compare flow, override the command file:
RRT_WINEDBG_CMD_FILE=/home/jan/projects/rrt/tools/winedbg_auto_load_compare.cmd \
tools/run_hook_auto_load_winedbg.sh hh
Or use the shorter wrapper:
tools/run_hook_auto_load_winedbg_compare.sh hh
If you do not use RRT_WINEDBG_CMD_FILE, you can still open those files and paste their contents into the debugger manually.
Both scripts rebuild rrt-hook, copy dinput8.dll into the Wine RT3 directory, and launch RT3 under winedbg.
Successful Manual Load
- Launch:
tools/run_rt3_winedbg.sh
-
The default command file now breaks on both:
0x004390cbfirst0x00445ac0second
-
In RT3, load save
hhmanually. -
The command file will dump:
- registers
- top-of-stack dwords
0x006cec740x006cec7c0x006cec780x006ce9b8..0x006ce9c40x006d1270..0x006d127c- backtrace
Focus on:
- whether the first hit is
0x004390cbor0x00445ac0 - caller address
ecx- the three stack arguments
0x006cec740x006cec7c0x006cec780x006ce9b8..0x006ce9c40x006d1270..
Failing Auto-Load Run
- Launch:
tools/run_hook_auto_load_winedbg.sh hh
-
The default command file now scripts a fuller non-interactive capture sequence:
0x004388900x004390cb0x00445ac00x0053fea6
-
Let the hook run.
-
The command file will dump the same register, stack, global, and backtrace state at the first hit.
-
Compare that output directly against the successful manual run.
So the current auto debugger path is now mostly headless:
- launch
tools/run_hook_auto_load_winedbg.sh hh - let the scripted breakpoints run
- inspect rt3_auto_load_winedbg.log
Manual typing is no longer required for the main auto-load comparison path unless we need an additional ad hoc breakpoint.
If the run still crashes and you need even earlier crash-side inspection after that, add one temporary extra breakpoint manually for:
0x00517cf0
Optional Host-Side GDB Fallback
If winedbg is too clumsy for repeated crashes, attach host gdb to the crashing Wine process after RT3 starts:
pgrep -af 'wine.*RT3.exe'
gdb -p <pid>
Useful commands in gdb:
set pagination off
handle SIGSEGV stop print
continue
bt
info registers
x/16wx $esp
This is mainly for cleaner backtraces after the fault PC is already known from winedbg.