https://bugs.winehq.org/show_bug.cgi?id=53682
--- Comment #10 from Kevin Puetz PuetzKevinA@JohnDeere.com --- Argh, sorry for the typos.
1. So we can see that syscall_frame is not at the bottom of the stack in KeUserModeCallback. 2. KiUserCallbackDispatcher is called back on the user stack, but the func it's invoking is a syscall, so we go back through _wine_syscall_dispatcher. 3. By the time the syscall machinery gets to win32u!NtUserCallOneParam, we are back on the original (syscall) stack, with exactly the same sp==syscall_frame. NtUserCallOneParam's prologue pushes its callee-save stuff, (same amount as KeUserModeCallback), and we enter the C function body with the same sp as KeUserModeCallback body, having trashed its values with our own.
*with* these traces executing, basically the same thing happens, but earlier - ntdll!__wine_dbg_write is *also* a -syscall, so now the first corruption now happens during the TRACE() call added to KiUserCallbackDispatcher, as the prologue of __wine_dbg_write now does the smashing. But it uses fewer registers and so it only smashes 0x21f530...0x21f53f, rather than the whole thing. Its call to __GI___libc_write smashes the rest.