[Bug 42114] Tonka Garage (DirectDraw3 game) crashes on exit (game incorrectly restores FPU control word/exception masks, causing Wine gdi32 DC code to trigger 'inexact result' FP exception)

WineHQ Bugzilla wine-bugs at winehq.org
Mon Jan 13 11:36:24 CST 2020


https://bugs.winehq.org/show_bug.cgi?id=42114

Anastasius Focht <focht at 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 at gmx.net
             Status|UNCONFIRMED                 |NEW

--- Comment #3 from Anastasius Focht <focht at 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

-- 
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.


More information about the wine-bugs mailing list