http://bugs.winehq.org/show_bug.cgi?id=28753
Bug #: 28753 Summary: AniDB O'Matic shows exception dialog on startup (madcodehook, gcc 4.6.x frame pointer omission in Wine code) Product: Wine Version: 1.3.30 Platform: x86 OS/Version: Linux Status: NEW Severity: normal Priority: P2 Component: -unknown AssignedTo: wine-bugs@winehq.org ReportedBy: focht@gmx.net Classification: Unclassified
Hello,
I recently switched to gcc 4.6.x based distro and encountered strange problems with "AniDB O'Matic" (http://anidb.net/ client) and possibly other apps.
The app crash handler catches a fault in startup phase and displays a rather exhaustive "crash analysis" dialog. The fault can be acknowledged and the app continues to load.
Tracing with +relay yields nothing - the crash never happens.
Without +relay but relevant debugging channels:
--- snip --- ... 0009:trace:ole:RemUnknown_Release 0x1aaa28 after: 4 0009:trace:ole:stub_manager_ext_addref added 5 refs to 0x1aac30 (oid 1), rc is now 5 0009:trace:ole:RPC_RegisterInterface ({00000131-0000-0000-c000-000000000046}) 0009:trace:ole:RPC_RegisterInterface Creating new interface 0009:trace:rpc:RpcServerRegisterIfEx (0x1aad34,(null),(nil),3,1234,(nil)) 0009:trace:rpc:RpcServerRegisterIf2 (0x1aad34,(null),(nil),3,1234,4294967295,(nil)) 0009:trace:rpc:RpcServerRegisterIf2 interface id: {00000131-0000-0000-c000-000000000046} 0.0 0009:trace:rpc:RpcServerRegisterIf2 transfer syntax: {00000000-0000-0000-0000-000000000000} 0.0 0009:trace:rpc:RpcServerRegisterIf2 dispatch table: 0x7e769c00 0009:trace:rpc:RpcServerRegisterIf2 dispatch table count: 1 0009:trace:rpc:RpcServerRegisterIf2 entry 0: 0x7e6a7010 0009:trace:rpc:RpcServerRegisterIf2 reserved: 0 0009:trace:rpc:RpcServerRegisterIf2 protseq endpoint count: 0 0009:trace:rpc:RpcServerRegisterIf2 default manager epv: (nil) 0009:trace:rpc:RpcServerRegisterIf2 interpreter info: (nil) 0009:trace:rpc:RPCRT4_start_listen 0009:trace:seh:raise_exception code=c0000005 flags=0 addr=0x44fed3 ip=0044fed3 tid=0009 0009:trace:seh:raise_exception info[0]=00000000 0009:trace:seh:raise_exception info[1]=000006bd 0009:trace:seh:raise_exception eax=000006b9 ebx=00404d74 ecx=00000000 edx=000006b9 esi=00000000 edi=7e63a728 0009:trace:seh:raise_exception ebp=0032f404 esp=0032f258 cs=0073 ds=007b es=007b fs=0033 gs=003b flags=00010a16 0009:trace:seh:call_vectored_handlers calling handler at 0x7dccdcf0 code=c0000005 flags=0 0009:trace:seh:call_vectored_handlers handler at 0x7dccdcf0 returned 0 0009:trace:seh:call_vectored_handlers calling handler at 0x7defb860 code=c0000005 flags=0 0009:trace:seh:call_vectored_handlers handler at 0x7defb860 returned 0 0009:trace:seh:call_stack_handlers calling handler at 0x45015e code=c0000005 flags=0 --- snip ---
By debugging I found the following:
--- snip --- Wine-dbg>bt Backtrace: =>0 0x0044fe24 in aom (+0x4fe24) (0x0033f43c) 1 0x7ed9cf31 CreateThread+0x4b(sa=(nil), stack=0, start=0x7e5f1640, param=0x1aa8c0, flags=0, id=0x0(nil)) [/home/focht/projects/wine/wine-git/dlls/kernel32/thread.c:54] in kernel32 (0x000006b9) 2 0x7e5f1883 RPCRT4_start_listen_protseq.isra+0x82() in rpcrt4 (0x000006b9) 3 0x7e5f1a00 RPCRT4_start_listen+0xdf(auto_listen=<is not available>) [/home/focht/projects/wine/wine-git/dlls/rpcrt4/rpc_server.c:749] in rpcrt4 (0x7e625720) 4 0x7e5f40c1 RpcServerRegisterIf2+0x170(IfSpec=0x1aabb4, MgrTypeUuid=(nil), MgrEpv=0x0(nil), Flags=0x3, MaxCalls=0x4d2, MaxRpcSize=0xffffffff, IfCallbackFn=(nil)) [/home/focht/projects/wine/wine-git/dlls/rpcrt4/rpc_server.c:1149] in rpcrt4 (0x00000000) --- snip ---
The app installs some hooks into win32 API using intrusive way.
No IAT/API entry/hotpatch is used .. it analyses the API code and patches calls.
Example:
"CreateThread" snippet:
--- snip original --- ... movl 0x38(%esp),%eax movl %eax,0xc(%esp) movl 0x34(%esp),%eax movl %eax,0x8(%esp) movl 0x30(%esp),%eax movl %eax,0x4(%esp) call 0x7edb1d80 CreateRemoteThread [/home/kernel32/thread.c:54] in kernel32: subl $28,%esp addl $40,%esp popl %ebx ret $0x18 --- snip original ---
--- snip patched --- ... movl 0x38(%esp),%eax movl %eax,0xc(%esp) movl 0x34(%esp),%eax movl %eax,0x8(%esp) movl 0x30(%esp),%eax movl %eax,0x4(%esp) call 0x0045029c subl $28,%esp addl $40,%esp popl %ebx ret $0x18 --- snip patched ---
Although questionable this works (even in earlier Wine versions). Further analysis reveals the app was written in Delphi and makes use of "madCodeHook/madExcept" library (http://madshi.net/).
The apps creates several threads which are initialized successfully. The problem is actually an RPC thread that gets created by Wine code.
See here:
http://source.winehq.org/git/wine.git/blob/7e309601f3cb55deadaab9bd444757483...
Disassembly of problematic snippet from function "RPCRT4_start_listen_protseq":
--- snip --- ... movl $0x6b9,%ebp movl $0x0,0x14(%esp) movl $0x0,0x10(%esp) movl %esi,0xc(%esp) movl %eax,0x8(%esp) movl $0x0,0x4(%esp) movl $0x0,0x0(%esp) call 0x7e5b9d68 CreateThread in rpcrt4 subl $24,%esp testl %eax,%eax ... --- snip ---
Upon entry of CreateRemoteThread "hook", the handler analyses the frame pointer (checks for non-zero) and tries to access the frame location. This fails because GCC emitted code that used EBP as general purpose register for return status (RPC_S_OUT_OF_RESOURCES) in caller. The fault due to EBP dereference is safely caught from app SEH and a dialog shown.
Additionally to the RPC thread problem the GUI shows issues which were previously not present: some treeview items are missing/rearranged.
I checked the comctl32/treeview code .. even did some tests with older Wine versions (1.3.0, 1.3.20) no regressions found - it behaved similar.
In short: these issues are the result of gcc 4.6+ now free to omit frame pointers on x86 (= default), see:
http://gcc.gnu.org/onlinedocs/gcc-4.6.1/gcc/Optimize-Options.html
I fixed the problems by adding "-fno-omit-frame-pointer" to build flags. The app works as expected (thread hooks = ok, GUI issues = gone).
I suspect there will be more apps and games that use code that depends on frame pointer not being abused as general purpose register.
The question is: is it worth to hunt down all Wine code and decorate functions with "no optimize" attributes to keep frame pointer from being abused? E.g. something like this: __attribute__((optimize("-fno-omit-frame-pointer")))
Maybe "-fno-omit-frame-pointer" should be default if Wine x86 is built with gcc 4.6.
Regards