https://bugs.winehq.org/show_bug.cgi?id=42114
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- Ever confirmed|0 |1 Component|-unknown |gdi32 Keywords| |download Summary|Tonka Garage: Unhandled |Tonka Garage (DirectDraw3 |exception upon exit |game) crashes on exit (game | |incorrectly restores FPU | |control word/exception | |masks, causing Wine gdi32 | |DC code to trigger 'inexact | |result' FP exception) CC| |focht@gmx.net Status|UNCONFIRMED |NEW
--- Comment #3 from Anastasius Focht focht@gmx.net --- Hello folks,
confirming.
Install:
--- snip --- $ cdemu load 0 TonkaGarage-1998-Win95.iso
$ cdemu status Devices' status: DEV LOADED FILENAME 0 True /home/focht/Downloads/TonkaGarage-1998-Win95.iso
$ udisksctl mount -b /dev/sr1 Mounted /dev/sr1 at /run/media/focht/TONKA_GR.
# new prefix $ rm -rf ~/.wine
$ wine explorer /desktop=tonka,1024x768 "e:\win95\tg_auto.exe" --- snip ---
--- snip --- $ pwd /home/focht/.wine/drive_c/Hasbro/TONKA_GR
$ file *
Models: directory TG_DEL95.EXE: PE32 executable (GUI) Intel 80386, for MS Windows TONKA_GR.CXT: data TONKA_GR.EXE: PE32 executable (GUI) Intel 80386, for MS Windows --- snip ---
Relevant part of trace log:
--- snip --- $ WINEDEBUG=+seh,+relay,+win,+msg,+clipping,+dc wine explorer /desktop=tonka,1024x768 ./TONKA_GR.exe >>log.txt 2>&1
... 002e:Call winex11.drv.CreateDesktopWindow(00010046) ret=7e85cb4d ... 002e:Ret winex11.drv.CreateDesktopWindow() retval=00000001 ret=7e85cb4d 002e:Ret user32.GetDesktopWindow() retval=00010046 ret=7e718583 ... 002e:Call user32.GetDC(00000000) ret=0048679d 002e:trace:win:GetDCEx hwnd 0x10046, hrgnClip (nil), flags 00000003 002e:Call user32.GetDpiForSystem() ret=7e7468cd 002e:Ret user32.GetDpiForSystem() retval=00000060 ret=7e7468cd 002e:trace:dc:CreateDCW (driver=L"DISPLAY", device=(null), output=(null)): returning 0x70041 ... 002e:Call user32.InvalidateRect(00020024,00000000,00000000) ret=0049a900 002e:trace:win:RedrawWindow 0x20024 whole window flags: RDW_INVALIDATE 002e:Ret user32.InvalidateRect() retval=00000001 ret=0049a900 002e:Call gdi32.SetDIBitsToDevice(00020052,00000000,00000000,00000280,000001e0,00000000,00000000,00000000,000001e0,02009560,02009138,00000000) ret=0049ab03 002e:Ret gdi32.SetDIBitsToDevice() retval=000001e0 ret=0049ab03 ... 002e:Call user32.GetDC(00000000) ret=00486962 002e:trace:win:GetDCEx hwnd 0x10046, hrgnClip (nil), flags 00000003 002e:trace:win:GetDCEx found valid 0x70041 hwnd 0x10046, flags 00000013 002e:trace:dc:SetHookFlags hDC 0x70041, flags 0010 002e:trace:dc:SetHookFlags hDC 0x70041, flags 0002 002e:Call winex11.drv.GetDC(00070041,00010046,00010046,0032fc80,0032fc90,00000013) ret=7e896c7e 002e:Ret winex11.drv.GetDC() retval=00000001 ret=7e896c7e 002e:trace:clipping:__wine_set_visible_region 0x70041 0x7c80069 (0,0)-(640,480) (0,0)-(0,0) (nil) 002e:trace:seh:raise_exception code=c000008f flags=0 addr=0x7d6e9e5c ip=7e6daa16 tid=002e 002e:trace:seh:raise_exception eax=012eafc0 ebx=012eafc0 ecx=012eafd0 edx=0032fbb0 esi=0032fbb0 edi=012eb0f4 002e:trace:seh:raise_exception ebp=0032fb98 esp=0032fb90 cs=0023 ds=002b es=002b fs=0063 gs=006b flags=00010206 002e:trace:seh:call_stack_handlers calling handler at 0x4f2938 code=c000008f flags=0 --- snip ---
Debugger:
--- snip --- $ winedbg --gdb ./TONKA_GR.exe
... Thread 1 received signal SIGFPE, Arithmetic exception. 0x7e6daa16 in construct_window_to_viewport (dc=0x12d4418, xform=0x32fbf0) at /home/focht/projects/wine/mainline-src/dlls/gdi32/dc.c:320 320 scaleX = (double)dc->vport_ext.cx / (double)dc->wnd_ext.cx;
Wine-gdb> bt #0 0x7e6daa16 in construct_window_to_viewport (dc=0x12d4418, xform=0x32fbf0) at /home/focht/projects/wine/mainline-src/dlls/gdi32/dc.c:320 #1 0x7e6df0fe in DC_UpdateXforms (dc=0x12d4418) at /home/focht/projects/wine/mainline-src/dlls/gdi32/dc.c:358 #2 0x7e6d9e33 in __wine_set_visible_region (hdc=<optimized out>, hrgn=<optimized out>, vis_rect=<optimized out>, device_rect=<optimized out>, surface=<optimized out>) at /home/focht/projects/wine/mainline-src/dlls/gdi32/clipping.c:296 #3 0x7e896d19 in update_visible_region (dce=0x12d2c80) at /home/focht/projects/wine/mainline-src/dlls/user32/painting.c:183 #4 0x7e8978f6 in GetDCEx (hwnd=<optimized out>, hrgnClip=<optimized out>, flags=19) at /home/focht/projects/wine/mainline-src/dlls/user32/painting.c:1118 #5 0x7e8983a6 in GetDC (hwnd=0x0) at /home/focht/projects/wine/mainline-src/dlls/user32/painting.c:1137 #6 0x00486962 in ?? () #7 0x00484580 in ?? () #8 0x004e5b23 in ?? () #9 0x7b452192 in call_process_entry () from /home/focht/projects/wine/mainline-install-x86_64/bin/../lib/wine/kernel32.dll.so #10 0x7b452580 in start_process (entry=<optimized out>, peb=<optimized out>) at /home/focht/projects/wine/mainline-src/dlls/kernel32/process.c:153 #11 0x7b45219e in __wine_start_process () from /home/focht/projects/wine/mainline-install-x86_64/bin/../lib/wine/kernel32.dll.so #12 0x00000000 in ?? ()
Wine-gdb> disas
Dump of assembler code for function construct_window_to_viewport: 0x7e6daa10 <+0>: push %ebp 0x7e6daa11 <+1>: mov %esp,%ebp 0x7e6daa13 <+3>: sub $0x8,%esp => 0x7e6daa16 <+6>: fildl 0x50(%eax) 0x7e6daa19 <+9>: fildl 0x40(%eax) 0x7e6daa1c <+12>: fdivrp %st,%st(1) 0x7e6daa1e <+14>: fildl 0x54(%eax) 0x7e6daa21 <+17>: fildl 0x44(%eax) 0x7e6daa24 <+20>: fdivrp %st,%st(1) 0x7e6daa26 <+22>: testb $0x1,0x98(%eax) 0x7e6daa2d <+29>: je 0x7e6daa38 <construct_window_to_viewport+40> 0x7e6daa2f <+31>: fxch %st(1) 0x7e6daa31 <+33>: fchs 0x7e6daa33 <+35>: jmp 0x7e6daa3a <construct_window_to_viewport+42> ...
Wine-gdb> p *dc $1 = {hSelf = 0x70041, nulldrv = {funcs = 0x7e7695a0 <null_driver>, next = 0x0, hdc = 0x70041}, physDev = 0x12d31e0, thread = 45, refcount = 1, dirty = 0, disabled = 0, saveLevel = 0, saved_dc = 0x0, dwHookData = 19737728, hookProc = 0x7e896e60 <dc_hook>, bounds_enabled = 0, path_open = 0, wnd_org = {x = 0, y = 0}, wnd_ext = {cx = 1, cy = 1}, vport_org = {x = 0, y = 0}, vport_ext = {cx = 1, cy = 1}, virtual_res = {cx = 0, cy = 0}, virtual_size = {cx = 0, cy = 0}, vis_rect = {left = 0, top = 0, right = 2560, bottom = 1080}, device_rect = {left = 0, top = 0, right = 0, bottom = 0}, pixel_format = 0, aa_flags = 5, miterLimit = 10, flags = 0, layout = 0, hClipRgn = 0x0, hMetaRgn = 0x0, hVisRgn = 0xa550062, region = 0x0, hPen = 0x10027, hBrush = 0x10020, hFont = 0x1002e, hBitmap = 0x1002a, hPalette = 0x10029, path = 0x0, font_gamma_ramp = 0x12d4598, font_code_page = 1252, ROPmode = 13, polyFillMode = 1, stretchBltMode = 1, relAbsMode = 1, backgroundMode = 2, backgroundColor = 16777215, textColor = 0, dcBrushColor = 16777215, dcPenColor = 0, brush_org = {x = 0, y = 0}, mapperFlags = 0, textAlign = 0, charExtra = 0, breakExtra = 0, breakRem = 0, MapMode = 1, GraphicsMode = 1, pAbortProc = 0x0, cur_pos = {x = 0, y = 0}, ArcDirection = 1, xformWorld2Wnd = {eM11 = 1, eM12 = 0, eM21 = 0, eM22 = 1, eDx = 0, eDy = 0}, xformWorld2Vport = {eM11 = 1, eM12 = 0, eM21 = 0, eM22 = 1, eDx = 0, eDy = 0}, xformVport2World = {eM11 = 1, eM12 = 0, eM21 = 0, eM22 = 1, eDx = 0, eDy = 0}, vport2WorldValid = 1, bounds = {left = 2147483647, top = 2147483647, right = -2147483648, bottom = -2147483648}}
Wine-gdb> p dc->vport_ext $2 = {cx = 1, cy = 1}
Wine-gdb> p dc->wnd_ext $3 = {cx = 1, cy = 1} --- snip ---
The game code incorrectly messes with the FPU control word. It doesn't use any msvcrt API (_control87, _controlfp, __control87_2) so it's a bit harder to find all places.
The FPU control word and status at the point when Wine code triggers the 'inexact result' FP exception by loading integer(1) and converting it to double extended-precision floating-point format:
x87ControlWord : 0040 = 0000'0000'0100'0000 (PC = 00 = 24 bits = REAL4, IEM = 1, PM = 0) x87StatusWord : C0A0 = 0000'0000'1010'0000
bit5 - PM - Precision Interrupt Mask (exception) 0: Generate INT/IRQ (disable handling at the FPU)
The game code that writes the FPU control word:
--- snip --- 0049E8B0 | 55 | push ebp | 0049E8B1 | 8BEC | mov ebp,esp | 0049E8B3 | D96D 08 | fldcw word ptr ss:[ebp+8] | 0049E8B6 | 5D | pop ebp | 0049E8B7 | C3 | ret | ... 0049F6C0 | 56 | push esi | 0049F6C1 | 8BF1 | mov esi,ecx | 0049F6C3 | 8B46 54 | mov eax,dword ptr ds:[esi+54] | 0049F6C6 | 85C0 | test eax,eax | 0049F6C8 | 74 0E | je tonka_gr.49F6D8 | 0049F6CA | 50 | push eax | 0049F6CB | FF15 64F04F00 | call dword ptr ds:[4FF064] | DeleteDC 0049F6D1 | C746 54 00000000 | mov dword ptr ds:[esi+54],0 | 0049F6D8 | A1 58BF5300 | mov eax,dword ptr ds:[53BF58] | get old fpword 0049F6DD | 50 | push eax | 0049F6DE | E8 CDF1FFFF | call tonka_gr.49E8B0 | fldcw 0049F6E3 | 83C4 04 | add esp,4 | 0049F6E6 | 5E | pop esi | 0049F6E7 | C3 | ret | --- snip ---
fpword variable location:
--- snip --- 0053BF58 00000000 --- snip ---
Memory address 0x0053BF58 referenced by:
--- snip --- Address Disassembly
0049F5E0 mov dword ptr ds:[53BF58],eax 0049F6D8 mov eax,dword ptr ds:[53BF58] --- snip ---
The game code that saves the fpword:
--- snip --- 0049E890 | 55 | push ebp | 0049E891 | 8BEC | mov ebp,esp | 0049E893 | 51 | push ecx | 0049E894 | C745 FC 00000000 | mov dword ptr ss:[ebp-4],0 | 0049E89B | 9B | fwait | 0049E89C | D97D FC | fnstcw word ptr ss:[ebp-4] | 0049E89F | 8B45 FC | mov eax,dword ptr ss:[ebp-4] | 0049E8A2 | 8BE5 | mov esp,ebp | 0049E8A4 | 5D | pop ebp | 0049E8A5 | C3 | ret | ... 0049F5D0 | 83EC 44 | sub esp,44 | 0049F5D3 | 53 | push ebx | 0049F5D4 | 55 | push ebp | 0049F5D5 | 56 | push esi | 0049F5D6 | 57 | push edi | 0049F5D7 | 8BD9 | mov ebx,ecx | 0049F5D9 | 33ED | xor ebp,ebp | 0049F5DB | E8 B0F2FFFF | call tonka_gr.49E890 | 0049F5E0 | A3 58BF5300 | mov dword ptr ds:[53BF58],eax | save old fpword 0049F5E5 | B9 10000000 | mov ecx,10 | 0049F5EA | 33C0 | xor eax,eax | 0049F5EC | 8D7C24 10 | lea edi,dword ptr ss:[esp+10] | 0049F5F0 | F3:AB | rep stosd | --- snip ---
The 'fpsave' code path is not hit at all under Wine. When the game exits, the default init value (0) is restored back.
Before:
x87ControlWord : 027F = 0000'0010'0111'1111 (PC = 10 = 53 bits = REAL8, IEM = 1, PM = 1, ...)
bit5 - PM - Precision Interrupt Mask (exception) 1: Do not generate INT/IRQ (enable handling at the FPU)
x87StatusWord : 4020 = 0100'0000'0010'0000
---
After (fpword = 0):
x87ControlWord : 0040 = 0000'0000'0100'0000 (PC = 00 = 24 bits = REAL4, IEM = 1, PM = 0, ...)
bit5 - PM - Precision Interrupt Mask (exception) 0: Generate INT/IRQ (disable handling at the FPU)
x87StatusWord : C0A0 = 0000'0000'1010'0000
I found one code path leading to the fpsave path -> "IMT Printout" (contains calls to 'PrintDlg', 'StartDoc' API). The problem: there is no obvious in-game feature that could trigger this code path. I'm almost certain this is the case on Windows as well.
It's very likely that the game just gets away by chance under Windows because there is no floating point code in the userspace exit path that could trigger floating point exceptions (remaining win32 API callouts). Windows kernel code (ring0 user32/gdi32) is not subject to this problem by design.
I don't think rewriting user32/gdi32 code to not use floating point or using expensive FPU control word store/restore is viable here. My proposal: 'WONTFIX'.
You can use the following workaround to avoid the crash on exit using 'winedbg' in proxy mode:
--- snip --- $ pwd /home/focht/.wine/drive_c/Hasbro/TONKA_GR
$ winedbg --gdb ./TONKA_GR.EXE <<< "set {int}0x53bf58=0x27f"$'\n'cont$'\n' --- snip ---
Explanation:
* using heredoc to feed commands to Winedbg in GDB proxy mode via stdin * set fpword variable located at 0x53bf58 to sane CW value (let FPU handle exceptions) * continue (until the end)
The variable can't be preset/patched in the executable because it's located in the part of .data section that is not physically backed (virtual size > raw size), hence the "runtime" way of fixing ;-)
=====
ProtectionID scan for documentation:
--- snip --- -=[ ProtectionID v0.6.9.0 DECEMBER]=- (c) 2003-2017 CDKiLLER & TippeX Build 24/12/17-21:05:42 Ready... Scanning -> C:\Hasbro\TONKA_GR\TONKA_GR.EXE File Type : 32-Bit Exe (Subsystem : Win GUI / 2), Size : 1297408 (013CC00h) Byte(s) | Machine: 0x14C (I386) Compilation TimeStamp : 0x36549BFC -> Thu 19th Nov 1998 22:30:20 (GMT) [TimeStamp] 0x36549BFC -> Thu 19th Nov 1998 22:30:20 (GMT) | PE Header | - | Offset: 0x000000D0 | VA: 0x004000D0 | - [File Heuristics] -> Flag #1 : 00000000000001001100000000000000 (0x0004C000) [Entrypoint Section Entropy] : 6.46 (section #0) ".text " | Size : 0xFDD42 (1039682) byte(s) [DllCharacteristics] -> Flag : (0x0000) -> NONE [SectionCount] 4 (0x4) | ImageSize 0x143000 (1323008) byte(s) [VersionInfo] Company Name : Media Station. Inc. [VersionInfo] Product Name : Tonka Garage [VersionInfo] Product Version : v1.1/US [VersionInfo] File Description : Media Player [VersionInfo] File Version : T4.0r1 [VersionInfo] Original FileName : Tonka_gr.EXE [VersionInfo] Internal Name : Tonka_gr [VersionInfo] Legal Copyrights : © 1998 Media Station. Inc. All rights reserved. [ModuleReport] [IAT] Modules -> COMCTL32.dll | WINMM.dll | VERSION.dll | KERNEL32.dll | USER32.dll | GDI32.dll | WINSPOOL.DRV | comdlg32.dll | ADVAPI32.dll | SHELL32.dll | d3drm.dll | DDRAW.dll [CompilerDetect] -> Visual C++ 5.1 [!] File appears to have no protection or is using an unknown protection - Scan Took : 0.618 Second(s) [00000026Ah (618) tick(s)] [506 of 580 scan(s) done] --- snip ---
$ sha1sum TonkaGarage-1998-Win95.iso b5265258a9efcced696d5287ba03ef0d9d777509 TonkaGarage-1998-Win95.iso
$ du -sh TonkaGarage-1998-Win95.iso 231M TonkaGarage-1998-Win95.iso
$ wine --version wine-5.0-rc5
Regards