Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to debug a 16-bit Windows program running in OTVDM? #1000

Closed
ayuanx opened this issue Jul 11, 2021 · 42 comments · Fixed by #1002
Closed

How to debug a 16-bit Windows program running in OTVDM? #1000

ayuanx opened this issue Jul 11, 2021 · 42 comments · Fixed by #1002

Comments

@ayuanx
Copy link

ayuanx commented Jul 11, 2021

I've got "out of memory" error.

There is no output in OTVDM window except "version 2117".

What debugger can I use? Or is there any OTVDM internal debugging mechanism I can use?

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

I ran the program in Win98 VM and it worked well, so I know there is something wrong with OTVDM.

By static disassembling, I know the error happened after: CREATEBITMAP --> CREATECOMPATIBLEDC --> SELECTOBJECT --> DELETEDC.

But without the ability to debug real-time, I have no idea what exactly went wrong.

Could somebody help?

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

I use windbg to debug winevdm itself and watcom wdw for programs running in it. You really should run a trace though, see #226 (comment) .

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Thanks! That really helps.

It is probably worth putting that info into wiki, so that more people know the steps.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Maybe not a bug of OTVDM itself? It seems the failure is either the "GLOBALALLOC" or the "DOSVM_Int21Handler" before that.

0df4:0c28:trace:selector:SELECTOR_FreeBlock 170: (11bf,1)
0df4:0c28:trace:dll:fill_init_list 938: (Z:\ADVWIN\ADVWIN.EXE) - START
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\WINNLS.DLL) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\WINNLS.DLL) - END
0df4:0c28:trace:dll:fill_init_list 938: (Z:\ADVWIN\IPF.DLL) - START
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\KRNL386.EXE) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\KRNL386.EXE) - END
0df4:0c28:trace:dll:fill_init_list 954: (Z:\ADVWIN\IPF.DLL) - END
0df4:0c28:trace:dll:fill_init_list 938: (Z:\ADVWIN\ADM.DLL) - START
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\GDI.EXE) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\GDI.EXE) - END
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\MMSYSTEM.DLL) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\MMSYSTEM.DLL) - END
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\USER.EXE) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\USER.EXE) - END
0df4:0c28:trace:dll:fill_init_list 954: (Z:\ADVWIN\ADM.DLL) - END
0df4:0c28:trace:dll:fill_init_list 938: (Z:\ADVWIN\SDW.DLL) - START
0df4:0c28:trace:dll:fill_init_list 954: (Z:\ADVWIN\SDW.DLL) - END
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\VER.DLL) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\VER.DLL) - END
0df4:0c28:trace:dll:fill_init_list 938: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\WIN87EM.DLL) - START
0df4:0c28:trace:dll:fill_init_list 954: (D:\TOOLS\OTVDM\WINDOWS\SYSTEM\WIN87EM.DLL) - END
0df4:0c28:trace:dll:fill_init_list 954: (Z:\ADVWIN\ADVWIN.EXE) - END
0df4:0c28:trace:dll:NE_CallDllEntryPoint 821: Calling D:\TOOLS\OTVDM\WINDOWS\SYSTEM\KRNL386.EXE DllEntryPoint, cs:ip=1017:4f36
0df4:0c28:trace:dll:NE_CallDllEntryPoint 821: Calling D:\TOOLS\OTVDM\WINDOWS\SYSTEM\GDI.EXE DllEntryPoint, cs:ip=10ff:4e4e
0df4:0c28:trace:dll:NE_CallDllEntryPoint 821: Calling D:\TOOLS\OTVDM\WINDOWS\SYSTEM\MMSYSTEM.DLL DllEntryPoint, cs:ip=1187:1776
0df4:0c28:trace:dll:NE_CallDllEntryPoint 821: Calling D:\TOOLS\OTVDM\WINDOWS\SYSTEM\USER.EXE DllEntryPoint, cs:ip=111f:5b46
0c28:Ret KERNEL.91: INITTASK() retval=none ret=120f:0014 ds=143f
AX=0001 BX=0080 CX=abe4 DX=0001 SI=0000 DI=143e ES=11f7 EFL=00003246
0c28:Call KERNEL.30: WAITEVENT(0000) ret=120f:004d ds=143f
0c28:Ret KERNEL.30: WAITEVENT() retval=00000000 ret=120f:004d ds=143f
0c28:Call USER.5: INITAPP(143e) ret=120f:0056 ds=143f
0c28:Ret USER.5: INITAPP() retval=00000001 ret=120f:0056 ds=143f
0df4:0c28:trace:int21:DOSVM_Int21Handler 4274: AX=3001 BX=0080 CX=0000 DX=0001 SI=0000 DI=ab4e DS=143f ES=143f EFL=00003202
0df4:0c28:trace:int21:DOSVM_Int21Handler 4698: GET DOS VERSION - version flag requested
0df4:0c28:trace:int21:DOSVM_Int21Handler 5529: returning: AX=0005 BX=0812 CX=3456 DX=0001 SI=0000 DI=ab4e DS=143f ES=143f EFL=00003202
0c28:Call KERNEL.132: GETWINFLAGS() ret=120f:0068 ds=143f
0c28:Ret KERNEL.132: GETWINFLAGS() retval=00006c29 ret=120f:0068 ds=143f
0c28:Call KERNEL.95: LOADLIBRARY(143f1c26 "WIN87EM") ret=120f:391a ds=143f
0c28:Ret KERNEL.95: LOADLIBRARY() retval=00001537 ret=120f:391a ds=143f
0c28:Call WIN87EM.1: __FPMATH() ret=120f:392d ds=143f
AX=1537 BX=0000 CX=0009 DX=1dd8 SI=1dcc DI=1de4 ES=143f EFL=00003246
0df4:0c28:trace:int:_fpMath 344: (cs:eip=120f:392d es=143f bx=0000 ax=1537 dx=11dd8)
0c28:Ret WIN87EM.1: __FPMATH() retval=none ret=120f:392d ds=143f
AX=0000 BX=0000 CX=0009 DX=1dd8 SI=1dcc DI=1de4 ES=143f EFL=00003246
0c28:Call WIN87EM.1: __FPMATH() ret=120f:393b ds=143f
AX=38e6 BX=0003 CX=0009 DX=120f SI=1dcc DI=1de4 ES=143f EFL=00003246
0df4:0c28:trace:int:_fpMath 344: (cs:eip=120f:393b es=143f bx=0003 ax=38e6 dx=1120f)
0df4:0c28:trace:int:DOSVM_SetPMHandler16 647: Set protected mode interrupt vector 3e <- 120f:38e6
0c28:Ret WIN87EM.1: __FPMATH() retval=none ret=120f:393b ds=143f
AX=38e6 BX=0003 CX=0009 DX=120f SI=1dcc DI=1de4 ES=143f EFL=00003246
0df4:0c28:trace:int21:DOSVM_Int21Handler 4274: AX=4400 BX=0000 CX=017c DX=0014 SI=1dcc DI=1de4 DS=143f ES=143f EFL=00003202
0df4:0c28:trace:int21:DOSVM_Int21Handler 5527: failed, error 0

0df4:0c28:trace:int21:DOSVM_Int21Handler 5529: returning: AX=0001 BX=0000 CX=017c DX=0014 SI=1dcc DI=1de4 DS=143f ES=143f EFL=00003203
0c28:Call KERNEL.15: GLOBALALLOC(0002,00001000) ret=120f:40ca ds=143f
AX=1000 BX=1644 CX=0000 DX=0000 SI=1dcc DI=0000 ES=0000 EFL=00003246
0c28:Ret KERNEL.15: GLOBALALLOC() retval=none ret=120f:40ca ds=143f
AX=1476 BX=1476 CX=1476 DX=0000 SI=1dcc DI=0000 ES=0000 EFL=00003246

0c28:Call KERNEL.18: GLOBALLOCK(1476) ret=120f:40d4 ds=143f
0c28:Ret KERNEL.18: GLOBALLOCK() retval=14770000 ret=120f:40d4 ds=143f
0df4:0c28:trace:int21:DOSVM_Int21Handler 4274: AX=4400 BX=0001 CX=0200 DX=1477 SI=1dcc DI=1de4 DS=143f ES=143f EFL=00003202
0df4:0c28:trace:int21:DOSVM_Int21Handler 5527: failed, error 0

0df4:0c28:trace:int21:DOSVM_Int21Handler 5529: returning: AX=0001 BX=0001 CX=0200 DX=1477 SI=1dcc DI=1de4 DS=143f ES=143f EFL=00003203
0c28:Call KERNEL.49: GETMODULEFILENAME(143e,1477:0ba2,0050) ret=120f:39ac ds=143f
0c28:Ret KERNEL.49: GETMODULEFILENAME() retval=02980016 ret=120f:39ac ds=143f
0c28:Call USER.174: LOADICON(143e,00000001 #1) ret=1427:1248 ds=143f

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Wait, even though it says "GLOBALALLOC() retval=none", but AX=1476 has a value? Maybe I misunderstood this "retval"?

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

Retval is none in this case because the function takes a full register context so the value in AX is correct, see

void WINAPI WIN16_GlobalAlloc16(UINT16 flags, DWORD size, CONTEXT *context)
. Int21 function 0x4400 is IOCTL, http://www.ctyme.com/intr/rb-2820.htm . Failure is the really only way to handle that because nt doesn't really permit direct hardware access. If the program works in ntvdm then that is very unlikely to be the problem.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

By the way, does OTVDM call GLOBALFLAGS() constantly? I've seen millions of such calls over and over again, as if enumerating every possible handle.
Even though I've never seen these handles (e.g. 0006) allocated by GLOBALALLOC() in the trace.

0c28:Call KERNEL.22: GLOBALFLAGS(0006) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00074000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0007) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00074000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(000e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=000f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(000f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=000f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0016) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00174000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0017) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00174000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(001e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=001f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(001f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=001f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0026) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00274000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0027) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00274000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(002e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=002f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(002f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=002f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0036) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00374000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0037) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00374000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(003e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=003f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(003f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=003f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0046) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00474000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0047) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00474000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(004e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=004f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(004f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=004f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0056) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00574000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(0057) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=00574000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(005e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=005f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(005f) ret=14af:01aa ds=14df

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

In win3 global handles really are global so that would enum all the global handles on the system. In winevdm (and ntvdm), it'll only get the global handles in the current vm. The program would have to be disassembled to know why. It could be a winevdm bug causing it to do that but I don't know.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Thanks for your help so far!

How can I see and break on the program's code inside OpenWatcom Debugger?

I can see the execution is always inside vm86.dll, but it seems it never reaches the 16-bit program under debug.
I guess it is probably because the 16-bit assembly code is running inside the VM, but I have no clue how to actually see it or break on it.

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

You need to use the win16 wdw.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

I see. I have to install win16 wdw as if my host OS were win16.

Even the OpenWatcom installer has to be emulated...... It never occurred to me. I thought the 16-bit installer wouldn't run.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

With wdw16, I can't even reach the original error. It seems exception is generated in ntdll.dll.

version: 2117
100c:fixme:user:LockInput16 4365: (0, 53, 1) - stub
100c:fixme:user:LockInput16 4365: (0, 53, 1) - stub
100c:fixme:user:LockInput16 4365: (0, 53, 1) - stub
SReg Load (032dde97): Segment is not a data segment or readable code segment.
SReg Load (032dde97): Segment is not a data segment or readable code segment.
=====dump all modules=====
Module Flags Name Flag
1c97 8021 VER SINGLEDATA | BUILTIN | LIBMODULE
1c6f 8309 SDW SINGLEDATA | FRAMEBUF | CONSOLE | GUI | LIBMODULE
1c17 8309 ADM SINGLEDATA | FRAMEBUF | CONSOLE | GUI | LIBMODULE
1bef 8309 IPF SINGLEDATA | FRAMEBUF | CONSOLE | GUI | LIBMODULE
1bd7 8021 WINNLS SINGLEDATA | BUILTIN | LIBMODULE
1967 030a ADVWIN MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
16bf 8021 CTL3DV2 SINGLEDATA | BUILTIN | LIBMODULE
15a7 0302 MADX86 MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
157f 8201 STD SINGLEDATA | CONSOLE | GUI | LIBMODULE
1567 8021 TOOLHELP SINGLEDATA | BUILTIN | LIBMODULE
150f 0302 EXPORT MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
14b7 0302 MAPSYM MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
145f 0302 CODEVIEW MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
1407 0302 WATCOM MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
13a7 0302 DWARF MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
1277 8021 COMMDLG SINGLEDATA | BUILTIN | LIBMODULE
1257 8021 WIN87EM SINGLEDATA | BUILTIN | LIBMODULE
11af 0302 WV MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
1197 8021 SOUND SINGLEDATA | BUILTIN | LIBMODULE
117f 8021 MMSYSTEM SINGLEDATA | BUILTIN | LIBMODULE
1167 8021 MOUSE SINGLEDATA | BUILTIN | LIBMODULE
114f 8021 KEYBOARD SINGLEDATA | BUILTIN | LIBMODULE
1137 8021 DISPLAY SINGLEDATA | BUILTIN | LIBMODULE
1117 8021 USER SINGLEDATA | BUILTIN | LIBMODULE
10f7 8021 GDI SINGLEDATA | BUILTIN | LIBMODULE
10cf 8021 TIMER SINGLEDATA | BUILTIN | LIBMODULE
10b7 8021 COMM SINGLEDATA | BUILTIN | LIBMODULE
109f 8021 SYSTEM SINGLEDATA | BUILTIN | LIBMODULE
1007 8021 KERNEL SINGLEDATA | BUILTIN | LIBMODULE
=====dump all modules=====
00A10000-00A19000 otvdm.exe
77B20000-77C8F000 ntdll.dll
75720000-75860000 KERNEL32.DLL
77810000-778E7000 KERNELBASE.dll
77030000-770AC000 ADVAPI32.dll
74160000-74174000 VCRUNTIME140.dll
74150000-74154000 api-ms-win-crt-stdio-l1-1-0.dll
74140000-74144000 api-ms-win-crt-runtime-l1-1-0.dll
74130000-74134000 api-ms-win-crt-convert-l1-1-0.dll
74120000-74123000 api-ms-win-crt-environment-l1-1-0.dll
74110000-74113000 api-ms-win-crt-heap-l1-1-0.dll
74100000-74105000 api-ms-win-crt-math-l1-1-0.dll
740F0000-740F3000 api-ms-win-crt-locale-l1-1-0.dll
75460000-75523000 msvcrt.dll
77A00000-77A41000 sechost.dll
75CA0000-75D5A000 RPCRT4.dll
740E0000-740E4000 api-ms-win-crt-string-l1-1-0.dll
75700000-7571E000 SspiCli.dll
74000000-740DF000 ucrtbase.DLL
752F0000-752FA000 CRYPTBASE.dll
75290000-752E4000 bcryptPrimitives.dll
73FC0000-73FFA000 libwine.dll
73FB0000-73FB3000 api-ms-win-crt-filesystem-l1-1-0.dll
73FA0000-73FA3000 api-ms-win-crt-utility-l1-1-0.dll
73F10000-73F94000 krnl386.exe16
73E80000-73F01000 DSOUND.dll
75300000-75454000 USER32.dll
75D60000-7701E000 SHELL32.dll
778F0000-779FE000 GDI32.dll
75180000-751E5000 WINSPOOL.DRV
75A80000-75AC5000 SHLWAPI.dll
75530000-75659000 ole32.dll
74CF0000-74D13000 WINMM.dll
73E40000-73E80000 POWRPROF.dll
77390000-7750C000 combase.dll
74A60000-74A83000 WINMMBASE.dll
75A40000-75A7C000 cfgmgr32.dll
74A20000-74A41000 DEVOBJ.dll
77360000-77387000 IMM32.DLL
75AD0000-75BE2000 MSCTF.dll
73E10000-73E38000 ntmarta.dll
73E00000-73E08000 system.drv16
73DF0000-73DF8000 comm.drv16
73DE0000-73DE8000 timer.drv16
73D50000-73DD7000 vm86.dll
777F0000-77804000 imagehlp.dll
73D20000-73D43000 gdi.exe16
73B70000-73D1A000 user.exe16
75280000-75288000 VERSION.dll
73B50000-73B66000 MPR.dll
748A0000-7498D000 UxTheme.dll
74880000-7489A000 dwmapi.dll
747C0000-747C9000 kernel.appcore.dll
73B40000-73B49000 display.drv16
73B30000-73B38000 keyboard.drv16
73B20000-73B28000 mouse.drv16
74220000-7435A000 PROPSYS.dll
77560000-775F7000 OLEAUT32.dll
73B00000-73B1B000 mmsystem.dll16
73AF0000-73AF9000 sound.drv16
73AE0000-73AE8000 ctl3dv2.dll16
73AD0000-73AD8000 ctl3d.dll16
73AC0000-73AC8000 win87em.dll16
73AA0000-73ABC000 commdlg.dll16
75660000-756FB000 COMDLG32.dll
751F0000-75279000 COMCTL32.dll
74990000-74A1C000 SHCORE.DLL
73920000-73A61000 dbghelp.dll
73A90000-73A99000 toolhelp.dll16
71710000-71719000 drprov.dll
716C0000-71705000 WINSTA.dll
716A0000-716B2000 ntlanman.dll
71680000-7169A000 davclnt.dll
71670000-7167A000 DAVHLPR.dll
71650000-71661000 wkscli.dll
731D0000-731DF000 cscapi.dll
71640000-7164A000 netutils.dll
71B60000-71B69000 winnls.dll16
71B50000-71B59000 ver.dll16
71B40000-71B43000 LZ32.dll
21: vm86.dll!load_x87function+0x7141 - 0x73d9bb40 0x73da2c81 (null):0
20: ntdll.dll!RtlDeleteAce+0x1d2 - 0x77b26650 0x77b26822 (null):0
19: ntdll.dll!LdrUnloadDll+0x181 - 0x77b8ce40 0x77b8cfc1 (null):0
18: ntdll.dll!KiUserExceptionDispatcher+0xf - 0x77b60420 0x77b6042f (null):0

17: vm86.dll!disassemble_debug+0x48e - 0x73da3610 0x73da3a9e (null):0
16: vm86.dll!disassemble_debug+0xd75 - 0x73da3610 0x73da4385 (null):0
15: vm86.dll!wine_call_to_16_regs_vm86+0x53 - 0x73da3300 0x73da3353 (null):0
14: krnl386.exe16!K32WOWCallback16Ex+0x46c - 0x73f65a90 0x73f65efc (null):0
13: krnl386.exe16!MapHModuleSL+0x1c8e - 0x73f4c240 0x73f4dece (null):0
12: krnl386.exe16!MapHModuleSL+0x1d58 - 0x73f4c240 0x73f4df98 (null):0
11: krnl386.exe16!MapHModuleSL+0x1d3d - 0x73f4c240 0x73f4df7d (null):0
10: krnl386.exe16!InitTask16+0x17a - 0x73f57b50 0x73f57cca (null):0
9: krnl386.exe16!RegFlushKey16+0x703 - 0x73f4f3a0 0x73f4faa3 (null):0
8: krnl386.exe16!vm_debug_get_entry_point+0x80c - 0x73f4fb00 0x73f5030c (null):0
7: vm86.dll!disassemble_debug+0x11b5 - 0x73da3610 0x73da47c5 (null):0
6: vm86.dll!wine_call_to_16_regs_vm86+0x53 - 0x73da3300 0x73da3353 (null):0
5: krnl386.exe16!K32WOWCallback16Ex+0x46c - 0x73f65a90 0x73f65efc (null):0
4: krnl386.exe16!LoadModule16+0x7a5 - 0x73f49be0 0x73f4a385 (null):0
3: krnl386.exe16!RestoreThunkLock+0xecf - 0x73f565d0 0x73f5749f (null):0
2: KERNEL32.DLL!BaseThreadInitThunk+0x24 - 0x757369f0 0x75736a14 (null):0
1: ntdll.dll!RtlInitializeExceptionChain+0x8f - 0x77b7aac0 0x77b7ab4f (null):0
0: ntdll.dll!RtlInitializeExceptionChain+0x5a - 0x77b7aac0 0x77b7ab1a (null):0
cs:ip=1597:07df bp=ead8 args(1bcf,0000,0000,1c67,1c67,1c66,0000,0000,0000,eb06)
cs:ip=1597:095e bp=eb06 args(06ca,156f,0000,0003,0000,0000,1c2f,3202,0458,102f)
cs:ip=1597:0000 bp=eb48 args(199f,0458,102f,0000,0000,0000,0000,0000,0000,0000)
cs:ip=199f:0014 bp=0001(call 1017:4218) args(0000,0000,abe4,eb4e,eb4e,0000,0000,ffff,1a30,1bcf)
cs:ip=199f:0000 bp=0000 args(0000,0000,0000,abe4,eb4e,eb4e,0000,0000,ffff,1a30)

address=778256E8
access address=73DABBFC
VM context
EAX:1D41,ECX:FFFF,EDX:159F159F,EBX:0000
ESP:EACA,EBP:EAD8,ESI:EACB,EDI:01C9
ES:1BCF,CS:1597,SS:1BCF,DS:159F,FS:0000,GS:0000
IP:07DF, address:73DBAC2D
EFLAGS:00003246

Interrupt 0D #GP (1597:07DD) flags 3246 err 1D40
mov es,ax

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

I managed to run wdw16 inside Win98 VM, and found the program actaully sends out tons of GLOBALFLAGS().

The program enumerates all handles between 0 - 0x8000, in a loop of GLOBALFLAGS() -> GLOBALSIZE() -> GLOBALPAGELOCK() -> GLOBALLOCK() -> GLOBALUNLOCK() -> GLOBALPAGEUNLOCK().

If any of them can proceed to GLOBALPAGELOCK() retval = NON_ZERO, it goes into the GOOD route.
If none of them can proceed this far or fails GLOBALPAGELOCK(), it goes into the error route.

It seems the problem is at GLOBALSIZE(). None of the global handles has a size greater or equal than 0x004B0000 (4MB), so it never even reaches GLOBALPAGELOCK().

In Win98 VM, we do have handle = 0x112E, size = 0x004B100000, which satisfies the requirement.

cseg02:0198
cseg02:0198 loc_468:                                ; CODE XREF: sub_456+FF�j
cseg02:0198                 mov     ax, si
cseg02:019A                 and     ax, 6
cseg02:019D                 cmp     ax, 6
cseg02:01A0                 jnz     loc_548
cseg02:01A4                 push    si              ; HGLOBAL
cseg02:01A5                 call    GLOBALFLAGS
cseg02:01AA                 and     ax, 0FF00h
cseg02:01AD                 cmp     ax, 4000h
cseg02:01B0                 jz      loc_548
cseg02:01B4                 push    si              ; HGLOBAL
cseg02:01B5                 call    GLOBALSIZE
cseg02:01BA                 shl     eax, 10h
cseg02:01BE                 shrd    eax, edx, 10h
cseg02:01C3                 cmp     eax, [bp+arg_8]  // = 0x004B0000
cseg02:01C7                 jb      loc_548
cseg02:01CB                 push    si              ; HGLOBAL
cseg02:01CC                 call    GLOBALPAGELOCK
cseg02:01D1                 or      ax, ax
cseg02:01D3                 jz      loc_548
cseg02:01D7                 push    si              ; HGLOBAL
cseg02:01D8                 call    GLOBALLOCK
cseg02:01DD                 mov     [bp+var_6], dx
cseg02:01E0                 mov     [bp+var_8], ax
cseg02:01E3                 mov     [bp+var_4], 0
cseg02:01EB                 jmp     short loc_532   <<<-------- If we can get here, we are good, otherwise error.

0c28:Call KERNEL.22: GLOBALFLAGS(1006) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=10070000 ret=14af:01aa ds=14df
0c28:Call KERNEL.20: GLOBALSIZE(1006) ret=14af:01ba ds=14df
0c28:Ret KERNEL.20: GLOBALSIZE() retval=000031c0 ret=14af:01ba ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(1007) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=10070000 ret=14af:01aa ds=14df
0c28:Call KERNEL.20: GLOBALSIZE(1007) ret=14af:01ba ds=14df
0c28:Ret KERNEL.20: GLOBALSIZE() retval=000031c0 ret=14af:01ba ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(100e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=100f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(100f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=100f4000 ret=14af:01aa ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(1016) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=10170000 ret=14af:01aa ds=14df
0c28:Call KERNEL.20: GLOBALSIZE(1016) ret=14af:01ba ds=14df
0c28:Ret KERNEL.20: GLOBALSIZE() retval=00005000 ret=14af:01ba ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(1017) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=10170000 ret=14af:01aa ds=14df
0c28:Call KERNEL.20: GLOBALSIZE(1017) ret=14af:01ba ds=14df
0c28:Ret KERNEL.20: GLOBALSIZE() retval=00005000 ret=14af:01ba ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(101e) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=101f0000 ret=14af:01aa ds=14df
0c28:Call KERNEL.20: GLOBALSIZE(101e) ret=14af:01ba ds=14df
0c28:Ret KERNEL.20: GLOBALSIZE() retval=00000040 ret=14af:01ba ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(101f) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=101f0000 ret=14af:01aa ds=14df
0c28:Call KERNEL.20: GLOBALSIZE(101f) ret=14af:01ba ds=14df
0c28:Ret KERNEL.20: GLOBALSIZE() retval=00000040 ret=14af:01ba ds=14df
0c28:Call KERNEL.22: GLOBALFLAGS(1026) ret=14af:01aa ds=14df
0c28:Ret KERNEL.22: GLOBALFLAGS() retval=10274000 ret=14af:01aa ds=14df

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

Does this work in ntvdm? It's looking for a >64K global handle that's arg_8 in size. I suspect that it's win95 specific behavior that ntvdm doesn't replicate.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Unfortunately I can't run NTVDM. I am on Win8.1 x64.

NTVDM is a Feature on Demand and only supported on the x86 version of Windows. It is not supported on x64 and ARM versions of Windows, which do not support 16-bit x86 code of any kind, including DOS programs.

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

It's easy to create an arbitrary large ghandle but without knowing why it's searching for a particular one rather than just allocating it itself I'm not optimistic that it'll fix it.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

This is weird. The program does not call GLOBALALLOC() itself, but tries to find existed global handle that's bigger than 4MB.
(Probably some anti-debug protection since it crashes wdw16 inside OTVDM. In Win98 VM, I also have exception errors with wdw16, but if I choose to ignore those, the program still runs.)

Thanks anyway!

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

I traced a bit more in Win98 VM, and found the program actually calls GLOBALALLOC() not far before that with a size request of 0x004B0000!

It is calculating the display buffer(?) of 1280 x 960 x 32-bit color = 0x0012C000 x 4 bytes = 0x004B0000.

It seems we only get 8-bit color (1 byte) in OTVDM?

0c28:Call USER.173: LOADCURSOR(0000,00007f81 #7f81) ret=14af:1acd ds=14df
0c28:Ret  USER.173: LOADCURSOR() retval=00b31616 ret=14af:1acd ds=14df
0c28:Call USER.69: SETCURSOR(1616) ret=14af:1ad3 ds=14df
0c28:Ret  USER.69: SETCURSOR() retval=000015e6 ret=14af:1ad3 ds=14df
0c28:Call KERNEL.15: GLOBALALLOC(0042,0012c000) ret=14af:014a ds=14df   <<--- Here! In Win98 VM, the size = 0x004B0000
     AX=e8ee BX=0008 CX=0000 DX=c000 SI=004e DI=0001 ES=143f EFL=00003202
0c28:Ret  KERNEL.15: GLOBALALLOC() retval=none ret=14af:014a ds=14df
     AX=161e BX=161e CX=161e DX=c000 SI=004e DI=0001 ES=0000 EFL=00003202
0c28:Call KERNEL.18: GLOBALLOCK(161e) ret=14af:1b06 ds=14df
0c28:Ret  KERNEL.18: GLOBALLOCK() retval=161f0000 ret=14af:1b06 ds=14df

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

The software isn't set for 256 color compat mode is it? Can you zip and post the whole trace?

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Sorry, scratch that. That's because I wasn't able to run it, so I set the compatibility mode to 8-bit color for another try.

After I removed that compatibility mode, it is allocating 4MB display buffer.

1254:Call USER.173: LOADCURSOR(0000,00007f81 #7f81) ret=14af:1acd ds=14df
1254:Ret  USER.173: LOADCURSOR() retval=00601616 ret=14af:1acd ds=14df
1254:Call USER.69: SETCURSOR(1616) ret=14af:1ad3 ds=14df
1254:Ret  USER.69: SETCURSOR() retval=000015e6 ret=14af:1ad3 ds=14df
1254:Call KERNEL.15: GLOBALALLOC(0042,004b0000) ret=14af:014a ds=14df   <<----- 4MB Allocated
     AX=e8ee BX=0020 CX=0000 DX=0000 SI=004e DI=0001 ES=143f EFL=00003202
1254:Ret  KERNEL.15: GLOBALALLOC() retval=none ret=14af:014a ds=14df
     AX=161e BX=161e CX=161e DX=0000 SI=004e DI=0001 ES=0000 EFL=00003202
1254:Call KERNEL.18: GLOBALLOCK(161e) ret=14af:1b06 ds=14df
1254:Ret  KERNEL.18: GLOBALLOCK() retval=161f0000 ret=14af:1b06 ds=14df
1254:Call USER.13: GETTICKCOUNT() ret=14af:1b11 ds=14df
1254:Ret  USER.13: GETTICKCOUNT() retval=029da31f ret=14af:1b11 ds=14df
1254:Call KERNEL.19: GLOBALUNLOCK(161e) ret=14af:1b49 ds=14df
1254:Ret  KERNEL.19: GLOBALUNLOCK() retval=161e0000 ret=14af:1b49 ds=14df
1254:Call GDI.48: CREATEBITMAP(0500,03c0,0001,0020,161f:0000) ret=14af:1b69 ds=14df
1254:Ret  GDI.48: CREATEBITMAP() retval=00000030 ret=14af:1b69 ds=14df
1254:Call KERNEL.17: GLOBALFREE(161e) ret=14af:1b7c ds=14df  <<-------------------- But soon freed
0bc4:1254:trace:selector:SELECTOR_FreeBlock 170: (161f,75)
1254:Ret  KERNEL.17: GLOBALFREE() retval=00000000 ret=14af:1b7c ds=14df
1254:Call GDI.52: CREATECOMPATIBLEDC(0000) ret=14af:1bb9 ds=14df
1254:Ret  GDI.52: CREATECOMPATIBLEDC() retval=00000034 ret=14af:1bb9 ds=14df
1254:Call GDI.45: SELECTOBJECT(0034,0030) ret=14af:1bd1 ds=14df
1254:Ret  GDI.45: SELECTOBJECT() retval=00000038 ret=14af:1bd1 ds=14df
1254:Call KERNEL.22: GLOBALFLAGS(0006) ret=14af:01aa ds=14df  <<--- Yet the program insists find exactly that 161e (allocated above) global handle
1254:Ret  KERNEL.22: GLOBALFLAGS() retval=00074000 ret=14af:01aa ds=14df
1254:Call KERNEL.22: GLOBALFLAGS(0007) ret=14af:01aa ds=14df
1254:Ret  KERNEL.22: GLOBALFLAGS() retval=00074000 ret=14af:01aa ds=14df
1254:Call KERNEL.22: GLOBALFLAGS(000e) ret=14af:01aa ds=14df
1254:Ret  KERNEL.22: GLOBALFLAGS() retval=000f4000 ret=14af:01aa ds=14df
1254:Call KERNEL.22: GLOBALFLAGS(000f) ret=14af:01aa ds=14df
...................
1254:Call KERNEL.22: GLOBALFLAGS(161e) ret=14af:01aa ds=14df
1254:Ret  KERNEL.22: GLOBALFLAGS() retval=161f4000 ret=14af:01aa ds=14df  <<-- Found it, but it didn't pass because the return value should not be 0x4000 (GMEM_DISCARDED). It is expecting a value of 0. (How??????? But in Win98 VM, it is 0x0!)
0c28:Call KERNEL.22: GLOBALFLAGS(161f) ret=14af:01aa ds=14df
0c28:Ret  KERNEL.22: GLOBALFLAGS() retval=161f4000 ret=14af:01aa ds=14df
cseg02:01A4                 push    si              ; HGLOBAL
cseg02:01A5                 call    GLOBALFLAGS
cseg02:01AA                 and     ax, 0FF00h
cseg02:01AD                 cmp     ax, 4000h  <<-------If it is 0x4000, it would fail and try next handle.
cseg02:01B0                 jz      loc_548
cseg02:01B4                 push    si              ; HGLOBAL
cseg02:01B5                 call    GLOBALSIZE
cseg02:01BA                 shl     eax, 10h
cseg02:01BE                 shrd    eax, edx, 10h
cseg02:01C3                 cmp     eax, [bp+arg_8]  // = 0x004B0000
cseg02:01C7                 jb      loc_548

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

Winevdm returns 0x4000 for freed handles which is wrong but fixing it won't help here because it's actually freeing the handle. What happens there in win98? If it frees the handle but still finds it I don't know the correct way to handle that.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

In Win98 VM, GLOBALALLOC() of 4MB succeeds, then it calls GLOBALLOCK(), and goes on to call GLOBALUNLOCK(), and finally calls GLOBALFREE().

That explains why later it finds exactly that handle and GLOBALFLAGS() returns 0 (not discarded).

FYI: 0x4000 == GMEM_DISCARDED.

I am confused by this behavior.

In OTVDM, GLOBALLOCK() succeeds, but GLOBALUNLOCK() fails, yet successfully freed nevertheless?

1254:Call KERNEL.18: GLOBALLOCK(161e) ret=14af:1b06 ds=14df
1254:Ret  KERNEL.18: GLOBALLOCK() retval=161f0000 ret=14af:1b06 ds=14df   <-- succeeds, returns an address 
1254:Call USER.13: GETTICKCOUNT() ret=14af:1b11 ds=14df
1254:Ret  USER.13: GETTICKCOUNT() retval=029da31f ret=14af:1b11 ds=14df
1254:Call KERNEL.19: GLOBALUNLOCK(161e) ret=14af:1b49 ds=14df
1254:Ret  KERNEL.19: GLOBALUNLOCK() retval=161e0000 ret=14af:1b49 ds=14df
1254:Call GDI.48: CREATEBITMAP(0500,03c0,0001,0020,161f:0000) ret=14af:1b69 ds=14df
1254:Ret  GDI.48: CREATEBITMAP() retval=00000030 ret=14af:1b69 ds=14df
1254:Call KERNEL.17: GLOBALFREE(161e) ret=14af:1b7c ds=14df
0bc4:1254:trace:selector:SELECTOR_FreeBlock 170: (161f,75)
1254:Ret  KERNEL.17: GLOBALFREE() retval=00000000 ret=14af:1b7c ds=14df  

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

Ignore the upper 16 bits for the return from globalunlock. There are programs that expect the handle to be there but the lower 16 bits being 0 means it succeeded. Wait, it's calling createbitmap(1280, 960, 1, 32). I bet it expects the bitmap to be placed into a global handle which would explains why later it's searches for a handle that size. This could be emulated by using createdibsection with global handle that points to the dib.

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

Another strange thing (not sure if related):

1254:Call KERNEL.18: GLOBALLOCK(161e) ret=14af:1b06 ds=14df
1254:Ret  KERNEL.18: GLOBALLOCK() retval=161f0000 ret=14af:1b06 ds=14df
1254:Call USER.13: GETTICKCOUNT() ret=14af:1b11 ds=14df  <<--- In Win98 VM, wdw16 shows this function as "GETCURRENTTIME"
1254:Ret  USER.13: GETTICKCOUNT() retval=029da31f ret=14af:1b11 ds=14df
1254:Call KERNEL.19: GLOBALUNLOCK(161e) ret=14af:1b49 ds=14df
1254:Ret  KERNEL.19: GLOBALUNLOCK() retval=161e0000 ret=14af:1b49 ds=14df

@ayuanx
Copy link
Author

ayuanx commented Jul 11, 2021

I don't think the CREATEBITMAP() is related. Because GLOBALUNLOCK() was called before it and succeeded, which means you don't have its actual memory address any more. Besides, CREATEBITMAP() does not need any global handle.

I think the key is that GLOBALFREE() succeeded in OTVDM; but fails in Win98 (eax = 0x00010000), which means the program CAN find it and use it later.

PS: In Win98, GLOBALLOCK() fails (eax = 0), so we don't have this 4MB address from the beginning.

@cracyc
Copy link
Contributor

cracyc commented Jul 11, 2021

Is the globallock return you are looking at eax or dx:ax? The first isn't correct because win16 doesn't use 32bit registers for any return.

@ayuanx
Copy link
Author

ayuanx commented Jul 12, 2021

Thanks, I just noticed that a minute ago.
I was wrong about the return value of GLOBALLOCK(). The return value is not just EAX, but DX:AX, so it was successful.
(The following is in Win98)

GLOBALLOCK() returns EAX=0, EDX=0x****7AFA

Then the program saves AX and DX.

later GLOBALUNLOCK() returns EAX=0, EDX=0x****7AF6

When it calls CREATEBITMAP(), it pushes 1280, 960, 1, 32, saved DX (0x7AFA), saved AX (0x0), returns a valid AX.

Finally GLOBALFREE() returns EAX=0x00100000, EDX=0 (not sure whether this is success or failure)

Later GLOBALFLAGS() on this handle (7AF6) returns EAX=0x00010000, EDX=0x004B6D87 and passes the AX != 0x4000 test.

So it seems the handle is freed in Win98 but not discarded?
In OTVDM, once the handle is freed, it is immediately discarded?

Is that the difference we can't meet?

@cracyc
Copy link
Contributor

cracyc commented Jul 12, 2021

So it seems the handle is freed in Win98 but not discarded?

This can't be quite right because I just tested on win311, win98 and ntvdm and on all of them globalfree freed the block immediately.

GLOBALLOCK() returns EAX=0, EDX=0x****7AFA

You sure about this? Selectors in win98 always have the lowest 3 bits set so need to end with 7 or f.

@ayuanx
Copy link
Author

ayuanx commented Jul 12, 2021

Oops, it is 7AF7. (I guess I typed it wrong.) Here is the screenshot, running in Win98SE VM.

Anyway, I patched the program to skip that GLOBALFREE(), then the program actually runs in OTVDM (meaning later it passed the GLOBALFLAGS() test on that (not freed) handle, but the client region of main window is black, nothing in it.
I can hear the music so something is still wrong with the graphics, probably related to this.

If I copy that patch into Win98, it shows the same thing--black window. (It seems this GLOBALFREE() is still needed?)

Clipboard02

@cracyc
Copy link
Contributor

cracyc commented Jul 12, 2021

I checked in win311 and it does allocate a global handle for the bitmap when using createbitmap so I'm 99% sure that's what is needed. When you use that patch it finds the buffer rather than the bitmap and so nothing gets copied to the screen.

@ayuanx
Copy link
Author

ayuanx commented Jul 12, 2021

In Win98, it runs like this:

GLOBALALLOC(0042,004B0000) ret AX: 713E

CREATEBITMAP() ret AX: 10F6
GLOBALFREE(713E) ret AX: 0  // 713E is freed.

SELECTOBJECT(10F6) ret AX: 072A  // Select bitmap into DC

GLOBALFLAGS(10F6) ret AX: 0  // The handle returned by CREATEBITMAP()
GLOBALSIZE(10F6) ret EAX: 0  // Fails 4MB size check, it loops back to try next handle

// The handles that pass above 4MB size check are F6, F7, 6136, 6137, 6616, 6617, 6BC6, 6BC7, 713E.
// And the loop breaks out at handle 713E (even though this handle was freed above.)

GLOBALFLAGS(713E) ret AX: 0  // The handle returned by GLOBALALLOC() but then freed by GLOBALFREE()
GLOBALSIZE(713E) ret DX_AX: 0x004B1000
GLOBALPAGELOCK(713E)
GLOBALLOCK(713E)   // Breaks out after this handle, no other handles are tested after this.
// I tried many times, it always breaks out at the freed handle. Sometimes the freed handle is a small value like 12F6,
// then it breaks out at this small handle value, other handle values greater than 12F6 are not tested at all.

In OTVDM, not a single handle passed the size check.
The freed handle which was expected to pass the size check, failed before size check at GLOBALFLAGS() ret AX: 0x4000 (discarded).
No handles reached GLOBALPAGELOCK().

@ayuanx
Copy link
Author

ayuanx commented Jul 12, 2021

My theory is that the program uses some hack to locate the screen buffer for fast drawing.

It first allocates a global handle exactly the size of the screen, locks it to get its address.
Then it retrieves current system tick, saves this tick value in a backup location, and writes this tick value twice into the first DWORD and second DWORD of the allocated global handle region, then it unlocks this handle.

It goes on to create a bitmap using this global handle address as the pointer to pixel color initialization values, frees the global handle, then selects this bitmap into DC. (Now the bitmap contains the system tick signature as if it were some color value.)

Now it starts to enumerate every possible global handle that is not discarded, also as large as the previously freed global handle.
When it finds one, it searches inside the region of the handle for those 2 DWORD signature tick values .

If no handle passes all these tests, it reports "out of memory".
If it finds one, it goes on to (I think) directly draw graphics in it.

This explains why it enumerates all handles and tries to find the tick signature in those handles, because if there is one, that must be the current screen buffer in use.

This can also explain why nothing is drawn on screen if I skip that GLOBALFREE, because then graphics is drawn into that global handle, which is not used by Windows as screen buffer (I think), because that handle is still owned by our program, not by Windows.

Somehow in Win98, the handle found is exactly the same handle the program freed earlier. (The program freed it, but Windows immediately picked it up and used it as screen buffer.)

@cracyc
Copy link
Contributor

cracyc commented Jul 12, 2021

Right, that's basically what I was thinking too (other than the timer part which I didn't notice in the picture above). If this is correct then it's effectively doing a createdibsection which didn't exist in win31. Also, that is basically what WinG does, if they had just used it this issue wouldn't exist.

@ayuanx
Copy link
Author

ayuanx commented Jul 12, 2021

Thank you for your help all the way!

@ayuanx ayuanx closed this as completed Jul 12, 2021
@cracyc
Copy link
Contributor

cracyc commented Jul 12, 2021

You didn't have to close this, if the theory is correct then it can be made to work without too much difficulty.

@cracyc
Copy link
Contributor

cracyc commented Jul 12, 2021

@ayuanx
Copy link
Author

ayuanx commented Jul 12, 2021

Thanks. I'll try and let you know.

@ayuanx
Copy link
Author

ayuanx commented Jul 13, 2021

Great news! Your method works.
No more "out of memory" and the graphics shows up in the program's window.

However it crashes when I close the program. I will look deeper and try to compare it in Win98 later.

version: 2119
SReg Load (032e394c): Segment is not a data segment or readable code segment.
=====dump all modules=====
Module Flags Name Flag
 1527   8021    WIN87EM     SINGLEDATA | BUILTIN | LIBMODULE
 150f   8021    VER         SINGLEDATA | BUILTIN | LIBMODULE
 14e7   8309    SDW         SINGLEDATA | FRAMEBUF | CONSOLE | GUI | LIBMODULE
 148f   8309    ADM         SINGLEDATA | FRAMEBUF | CONSOLE | GUI | LIBMODULE
 1467   8309    IPF         SINGLEDATA | FRAMEBUF | CONSOLE | GUI | LIBMODULE
 1447   8021    WINNLS      SINGLEDATA | BUILTIN | LIBMODULE
 11af   030a    ADVWIN      MULTIPLEDATA | FRAMEBUF | CONSOLE | GUI
 1197   8021    SOUND       SINGLEDATA | BUILTIN | LIBMODULE
 117f   8021    MMSYSTEM    SINGLEDATA | BUILTIN | LIBMODULE
 1167   8021    MOUSE       SINGLEDATA | BUILTIN | LIBMODULE
 114f   8021    KEYBOARD    SINGLEDATA | BUILTIN | LIBMODULE
 1137   8021    DISPLAY     SINGLEDATA | BUILTIN | LIBMODULE
 1117   8021    USER        SINGLEDATA | BUILTIN | LIBMODULE
 10f7   8021    GDI         SINGLEDATA | BUILTIN | LIBMODULE
 10cf   8021    TIMER       SINGLEDATA | BUILTIN | LIBMODULE
 10b7   8021    COMM        SINGLEDATA | BUILTIN | LIBMODULE
 109f   8021    SYSTEM      SINGLEDATA | BUILTIN | LIBMODULE
 1007   8021    KERNEL      SINGLEDATA | BUILTIN | LIBMODULE
=====dump all modules=====
00AC0000-00AC9000 otvdm.exe
77470000-775DF000 ntdll.dll
76610000-76750000 KERNEL32.DLL
760E0000-761B7000 KERNELBASE.dll
76D40000-76DBC000 ADVAPI32.dll
73400000-73414000 VCRUNTIME140.dll
733F0000-733F4000 api-ms-win-crt-stdio-l1-1-0.dll
733E0000-733E4000 api-ms-win-crt-runtime-l1-1-0.dll
733D0000-733D4000 api-ms-win-crt-convert-l1-1-0.dll
733C0000-733C3000 api-ms-win-crt-environment-l1-1-0.dll
733B0000-733B3000 api-ms-win-crt-heap-l1-1-0.dll
733A0000-733A5000 api-ms-win-crt-math-l1-1-0.dll
73390000-73393000 api-ms-win-crt-locale-l1-1-0.dll
76500000-765C3000 msvcrt.dll
76A10000-76A51000 sechost.dll
76930000-769EA000 RPCRT4.dll
73380000-73384000 api-ms-win-crt-string-l1-1-0.dll
76430000-7644E000 SspiCli.dll
732A0000-7337F000 ucrtbase.DLL
74C40000-74C4A000 CRYPTBASE.dll
74BE0000-74C34000 bcryptPrimitives.dll
72A30000-72A6A000 libwine.dll
73290000-73293000 api-ms-win-crt-filesystem-l1-1-0.dll
73280000-73283000 api-ms-win-crt-utility-l1-1-0.dll
729A0000-72A24000 krnl386.exe16
72910000-72991000 DSOUND.dll
76E50000-76FA4000 USER32.dll
74E10000-760CE000 SHELL32.dll
76B10000-76C1E000 GDI32.dll
74AD0000-74B35000 WINSPOOL.DRV
764B0000-764F5000 SHLWAPI.dll
761C0000-762E9000 ole32.dll
74640000-74663000 WINMM.dll
728D0000-72910000 POWRPROF.dll
76750000-768CC000 combase.dll
743B0000-743D3000 WINMMBASE.dll
74C50000-74C8C000 cfgmgr32.dll
74370000-74391000 DEVOBJ.dll
76E10000-76E37000 IMM32.DLL
76C20000-76D32000 MSCTF.dll
73650000-73678000 ntmarta.dll
728C0000-728C8000 system.drv16
728B0000-728B8000 comm.drv16
728A0000-728A8000 timer.drv16
72810000-72897000 vm86.dll
769F0000-76A04000 imagehlp.dll
727E0000-72803000 gdi.exe16
72630000-727DA000 user.exe16
74BD0000-74BD8000 VERSION.dll
72610000-72626000 MPR.dll
741F0000-742DD000 UxTheme.dll
741D0000-741EA000 dwmapi.dll
74110000-74119000 kernel.appcore.dll
72600000-72609000 display.drv16
725F0000-725F8000 keyboard.drv16
725E0000-725E8000 mouse.drv16
73800000-7393A000 PROPSYS.dll
76A60000-76AF7000 OLEAUT32.dll
725C0000-725DB000 mmsystem.dll16
725B0000-725B9000 sound.drv16
725A0000-725A8000 ctl3dv2.dll16
72590000-72598000 ctl3d.dll16
72580000-72589000 winnls.dll16
72570000-72579000 ver.dll16
72560000-72563000 LZ32.dll
72550000-72558000 win87em.dll16
73B60000-73CA1000 dbghelp.dll
724E0000-72533000 MMDevAPI.DLL
724A0000-724D6000 wdmaud.drv
72490000-72497000 ksuser.dll
72480000-7248A000 AVRT.dll
72420000-72480000 AUDIOSES.DLL
72410000-72419000 msacm32.drv
723F0000-72407000 MSACM32.dll
723E0000-723E8000 midimap.dll
12: vm86.dll!load_x87function+0x7141 - 0x7285bb40 0x72862c81 (null):0
11: ntdll.dll!RtlDeleteAce+0x1d2 - 0x77476650 0x77476822 (null):0
10: ntdll.dll!LdrUnloadDll+0x181 - 0x774dce40 0x774dcfc1 (null):0
9: ntdll.dll!KiUserExceptionDispatcher+0xf - 0x774b0420 0x774b042f (null):0

8: vm86.dll!disassemble_debug+0x48e - 0x72863610 0x72863a9e (null):0
7: vm86.dll!disassemble_debug+0xd75 - 0x72863610 0x72864385 (null):0
6: vm86.dll!wine_call_to_16_regs_vm86+0x53 - 0x72863300 0x72863353 (null):0
5: krnl386.exe16!K32WOWCallback16Ex+0x46c - 0x729f5b20 0x729f5f8c (null):0
4: krnl386.exe16!LoadModule16+0x7a5 - 0x729d9c60 0x729da405 (null):0
3: krnl386.exe16!RestoreThunkLock+0xecf - 0x729e6660 0x729e752f (null):0
2: KERNEL32.DLL!BaseThreadInitThunk+0x24 - 0x766269f0 0x76626a14 (null):0
1: ntdll.dll!RtlInitializeExceptionChain+0x8f - 0x774caac0 0x774cab4f (null):0
0: ntdll.dll!RtlInitializeExceptionChain+0x5a - 0x774caac0 0x774cab1a (null):0
cs:ip=14b7:004c bp=e8d6                 args(14af,0000,1877,0000,0000,0001,004e,0000,1877,0000)
cs:ip=14af:01fc bp=e8f1(call 14b7:0045) args(0f6e,14df,0f70,14df,0100,0002,53e2,02fa,e972,0180)
cs:ip=14af:1c1e bp=e923                 args(0120,0060,002f,e972,0180,4fb6,4030,7265,e9c6,087d)
cs:ip=14af:0ff1 bp=e94d                 args(0120,0060,0060,0060,002f,0a00,05a0,005d,004e,0041)
cs:ip=14af:089b bp=e9c7                 args(0a00,05a0,1206,1427,143f,eb3d,16d6,1427,0000,1de4)
cs:ip=14af:1110 bp=e9d7                 args(0000,1de4,1dcc,7782,7c81,6682,6882,7182,6b82,0000)
cs:ip=1427:16d6 bp=eb3d(call 14af:10f7) args(0001,0080,11f7,0000,143e,0000,0458,102f,0000,0000)
cs:ip=120f:00b6 bp=0001(call 1427:1276) args(0000,0000,abe4,eb4e,eb4e,0002,0000,ffff,1a30,143f)
cs:ip=120f:0000 bp=0000                 args(0000,0000,0000,abe4,eb4e,eb4e,0002,0000,ffff,1a30)

address=760F56E8
access address=7286BBFC
VM context
EAX:18770000,ECX:1877,EDX:4B1877,EBX:0000
ESP:E8D6,EBP:E8D6,ESI:1876,EDI:0000
ES:0000,CS:14B7,SS:143F,DS:14DF,FS:0000,GS:0000
IP:004C, address:7287AC2D
EFLAGS:00003283

Interrupt 0D #GP (14B7:0048) flags 3283 err 1874
lgs     bx,[bp+6h]

@ayuanx
Copy link
Author

ayuanx commented Jul 13, 2021

I think I know what was wrong.

When the program exits, it shows another "goodbye" window for a few seconds.

This "goodbye" window is created in the same hackish way as the main window. (actually using the same code routine).

At this point, the original main window and its bitmap should have been destroyed.

Crash point:

lgs bx,[bp+6h]

[bp+6h] stores the return DWORD value (i.e. DX:AX) of GLOBALLOCK(), which should be the memory address of a global handle.

It seems GLOBALLOCK() returned an invalid address (seg==0) even though this global handle already passed GLOBALFLAGS() "not discarded" test and GLOBALSIZE() "larger than the screen" test.

Not sure if your modification can support more than 1 of such screen buffer created by CREATEBITMAP().

This problematic handle must be the bitmap handle of the original main window, since we never mark it as "discarded", it will be found again, but the selectors are freed areadly at this stage.

I think we need to mark the handle "discarded" or change its size to 0, when its selector is freed. (Maybe not at GLOBALFREE(), but at segptr free)

@ayuanx
Copy link
Author

ayuanx commented Jul 13, 2021

It does this when the bitmap is deleted in free_segptr_bits when GlobalMapInternal clears the arena entry which is same thing that GLOBAL_FreeBlock does.

But the program still finds this global handle "not discarded" and try to load its selector (which is 0) into GS, and crash.

I haven't read all code of OTVDM, but I guess arena entry is different from the return value of GLOBALFLAGS()?
(Not sure if clearing arena entry will make GLOBALFLAGS() return "discarded".)

Or is it because of this bug?
cracyc@b2b014a#r53444759

@ayuanx ayuanx changed the title How to debug a 16-bit Windows program runnning in OTVDM? How to debug a 16-bit Windows program running in OTVDM? Jul 13, 2021
@ayuanx ayuanx reopened this Jul 14, 2021
@emxd
Copy link

emxd commented Dec 31, 2023

@cracyc

If you don't mind me asking, how would you go about bytepatching a variable within the emulated program during runtime? This is for a different program, not the one specified by OP, but since you discussed debugging with wdw at length here I thought I might ask.

I found the variable with ghidra and then looked for it with wdw, the variable being at DS:19F7 OFFSET:05C4 (that's the address when debugged with wdw), but I don't know how to translate it to an address within the otvdm.exe process. Is there any example of runtime bytepatching?

I tried it with BYTE *ptr = MapSL( MAKESEGPTR( 0x19F7, 0x05C4 ) ); but it gave me an address of 0x5c4 (and there's no module there)

Or is the segment address different than the one reported in wdw? The offset should be correct, ghidra shows an identical one.

edit: I can get the module handle's address with BYTE* ptr = MapSL(MAKESEGPTR(GetModuleHandle16("APP.EXE"), 0));, now just to get to the data segment :)

edit2: my bad, MapSL( MAKESEGPTR()) works, but the handle from wdw is different. I got the correct handles from NE_DumpAllModules()

@cracyc
Copy link
Contributor

cracyc commented Jan 1, 2024

Use windbg and break the program then use ".call krnl386!mapsl(segoff)" where segoff is the selector and offset as a single dword. That will give the linear address in the winevdm address space. I don't think wdw can provide selector base addresses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants