[Bug 48898] 4k demoscene OpenGL demo 'End of time' by Alcatraz and Altair crashes on startup due to missing opengl32.dll 'wglGetDefaultProcAddress' stub
WineHQ Bugzilla
wine-bugs at winehq.org
Mon Apr 13 16:01:39 CDT 2020
https://bugs.winehq.org/show_bug.cgi?id=48898
Anastasius Focht <focht at gmx.net> changed:
What |Removed |Added
----------------------------------------------------------------------------
Summary|4k demoscene OpenGL demo |4k demoscene OpenGL demo
|'End of time' by Alcatraz |'End of time' by Alcatraz
|and Altair crashes on |and Altair crashes on
|startup due to opengl32.dll |startup due to missing
|imports ordinal mismatch |opengl32.dll
| |'wglGetDefaultProcAddress'
| |stub
--- Comment #5 from Anastasius Focht <focht at gmx.net> ---
Hello Vijay,
--- quote ---
With the relay log and the patch from #42125 can we find the ordinals the demo
uses?
--- quote ---
well, no. It's actually more complicated. The Crinkler executable file
compressor implements a custom loader/imports resolver which doesn't use any
win32 API calls for resolving. Since everything needs to be kept small, it uses
a pretty clever approach, consuming only a minimum amount of information/data
storage.
--- snip ---
$ WINEDEBUG=+seh,+relay,+imports,+module,+opengl,+wgl wine ./End\ of\ time\
720p.exe >>log.txt 2>&1
...
0009:Call KERNEL32.LoadLibraryA(004208eb "opengl32") ret=004200a4
...
0009:Ret KERNEL32.LoadLibraryA() retval=7a840000 ret=004200a4
...
0009:Call opengl32.wglMakeCurrent(00421314,00008b30) ret=00420814
0009:Ret opengl32.wglMakeCurrent() retval=00000000 ret=00420814
0009:trace:seh:raise_exception code=c0000005 flags=0 addr=(nil) ip=00000000
tid=0009
0009:trace:seh:raise_exception info[0]=00000000
0009:trace:seh:raise_exception info[1]=00000000
0009:trace:seh:raise_exception eax=00000000 ebx=00400148 ecx=00421250
edx=00000000 esi=7a8477cc edi=00030039
0009:trace:seh:raise_exception ebp=1518ff00 esp=1518fee4 cs=0023 ds=002b
es=002b fs=0063 gs=006b flags=00010202
0009:trace:seh:call_stack_handlers calling handler at 0x7b469400 code=c0000005
flags=0
--- snip ---
As already said, relay trace yields no useful information. One has to debug it.
The easiest way to debug Crinkler executables is to set a hardware breakpoint
on execution at the decompression area for code. It's usually located at fixed
0x420000 address range for 32-bit.
After hitting the breakpoint you will come across the following code. I've
annotated the relevant disassembly snippet but will also describe it in a
summary after the snippet in case assembly it not your cup of tea ;-)
--- snip ---
; uncompressed entry
00420000 POP EDI
00420001 MOV ECX,1D
00420006 MOV AL,0E8
...
00420018 MOV EBX,OFFSET 00400108 ; hash names start
0042001D MOV ESI,OFFSET 004208CE ; hash imports ranges
00420022 MOV EDI,OFFSET 00430000 ; area for custom IAT
00420027 POP EAX ; PEB
00420028 MOV EAX,DWORD PTR DS:[EAX+0C] ; PEB_LDR_DATA
0042002B MOV EAX,DWORD PTR DS:[EAX+0C]
0042002E MOV EAX,DWORD PTR DS:[EAX]
00420030 MOV EAX,DWORD PTR DS:[EAX] ; Flink
; _LDR_DATA_TABLE_ENTRY.DllBase (kernel32)
00420032 MOV EBP,DWORD PTR DS:[EAX+18]
00420035 TEST EBP,EBP ; module load address != 0?
00420037 JNE SHORT 00420047
00420039 PUSH 0
0042003B PUSH 0
0042003D PUSH EDX
0042003E PUSH 0
00420040 CALL DWORD PTR DS:[43001C] ; user32.MessageBoxA
00420046 RETN
00420047 XOR EAX,EAX
00420049 LODS BYTE PTR DS:[ESI] ; symbol (imports) range counter
0042004A XCHG EAX,ECX
0042004B PUSHAD
0042004C MOV EAX,DWORD PTR SS:[EBP+3C] ; EBP = module address, DOS->e_lfanew
0042004F ADD EAX,EBP ; VA PE header
00420051 MOV EDX,DWORD PTR DS:[EAX+78] ; RVA export directory
00420054 ADD EDX,EBP ;
00420056 MOV ECX,DWORD PTR DS:[EDX+18] ; Exports: NumberOfNames
00420059 MOV EAX,DWORD PTR DS:[EDX+20] ; Exports: AddressOfNames
0042005C ADD EAX,EBP ;
0042005E MOV ESI,DWORD PTR DS:[ECX*4+EAX-4] ; RVA sym_name[entry]
00420062 ADD ESI,EBP ; VA sym_name[entry]
00420064 XOR EDI,EDI
00420066 ROL EDI,6 ; sym_hash #
00420069 XOR EAX,EAX
0042006B LODS BYTE PTR DS:[ESI] ; sym_name[entry][char]
0042006C XOR EDI,EAX ; sym_hash #
0042006E DEC EAX
0042006F JGE SHORT 00420066 ; reached null terminator?
00420071 CMP EDI,DWORD PTR DS:[EBX] ; hashed function name match?
00420073 LOOPNZ SHORT 00420059 ; entry-- ; next (top-down) entry
00420075 MOV EAX,DWORD PTR DS:[EDX+24] ; Exports: AddressOfNameOrdinals
00420078 ADD EAX,EBP
0042007A MOV CX,WORD PTR DS:[ECX*2+EAX]; ECX = number of function
0042007E MOV EAX,DWORD PTR DS:[EDX+1C] ; Exports: AddressOfFunctions
00420081 ADD EAX,EBP
00420083 LEA EAX,[ECX*4+EAX] ; RVA API entry
00420086 MOV DWORD PTR SS:[ESP+14],EAX
0042008A POPAD
0042008B MOV EAX,DWORD PTR DS:[EDX]
0042008D ADD EAX,EBP ; VA API entry
0042008F ADD EDX,4 ; entry++
00420092 STOS DWORD PTR ES:[EDI] ; store entry in custom IAT
00420093 DEC BYTE PTR DS:[ESI] ; range_entry counter--
00420095 JNE SHORT 0042008B
00420097 INC ESI ; next range index
00420098 ADD EBX,4 ; next hash
0042009B LOOP SHORT 0042004B ; range counter--, next iteration
0042009D PUSH ESI
0042009E CALL DWORD PTR DS:[430008] ; kernel32.LoadLibraryA
004200A4 XCHG EAX,EBP ; EBP = module load address
004200A5 MOV EDX,ESI
004200A7 LODS BYTE PTR DS:[ESI]
004200A8 DEC AL
004200AA JNS SHORT 004200A7
004200AC INC AL
004200AE JE SHORT 00420035
--- snip ---
First, let me recap the export directory for you since the relation of the
arrays/tables are important here. It points to 3 arrays/tables, containing
RVAs:
* AddressOfFunctions
* AddressOfNames
* AddressOfNameOrdinals
Some general properties of these arrays:
* 'Names' and 'NameOrdinals' arrays = same length = NumberOfNames
* 'Names' and 'NameOrdinals' arrays can be shorter than 'Functions' array if
functions are exported purely by ordinal. In extreme case they can be even
empty (all exports ordinals only)
* The Ordinal of each exported function is the index in 'Functions' array.
Important: starting from 1 (!)
* The relation between name and function address is done via 'NameOrdinals'
array. Each entry in 'NameOrdinals' is an index into 'Functions' array.
Important: starting from 0 (!)
---
As indicated in the preamble, the imports resolver uses a clever way to
minimize the amount of information needed to resolve/lookup function addresses.
After loading each dll, it traverses the export directory 'AddressOfNames'
array from top-down (last entry is processed first) and hashes each API
function name.
The hash algorithm from the disassembly can be written in C code like this:
--- snip ---
int result = 0x0;
while (*str) {
result = _rotl(result, 6);
result ^= *str++;
}
--- snip ---
Every bit of the input has to affect the result (character set of the string).
Basically the current result is rotated by a number of bits and then the
current result is XORed with the current string byte. Repeat until the end of
the string.
The compressor has two internal tables for each dll:
* pre-computed 32-bit hashes for API function names
* number of consecutive functions to import (range)
It compares the hashed value of the API function name to a pre-computed 32-bit
hash from above table. When it finds a match, it uses the 'NameOrdinals' array
for further lookup. The association of name and function address is done in
'NameOrdinals' array. Each entry in 'NameOrdinals' is an index into 'Functions'
array (starting from 0). The position of the entry in 'NameOrdinals' denotes
the position of name in 'Names' array with the name of the function.
In the case of 'opengl32.dll' only two ranges are used.
range #1: glBindTexture + 241 (0xf1) consecutive functions
range #2: glTexParameteri + 46 (0x2e) consecutive functions
To illustrate further, a dump of the resolver's custom IAT after processing:
--- snip ---
Address Value Comments
00430000 7B42F93C ; kernel32.CreateThread
00430004 7B45010C ; kernel32.ExitProcess
00430008 7B430DF0 ; kernel32.LoadLibraryA
0043000C 7E8910A0 ; user32.ChangeDisplaySettingsA
00430010 7E89A250 ; user32.CreateWindowExA
00430014 7E84FE90 ; user32.GetAsyncKeyState
00430018 7E87B320 ; user32.GetDC
0043001C 7E875C60 ; user32.MessageBoxA
00430020 7E8717E0 ; user32.PeekMessageA
00430024 7E82A7E0 ; user32.ShowCursor
00430028 7E597AA0 ; gdi32.ChoosePixelFormat
0043002C 7E597C40 ; gdi32.SetPixelFormat
00430030 7E597CD0 ; gdi32.SwapBuffers
; opengl32 imports range #1
00430034 7A8AD8D0 ; opengl32.glBindTexture
00430038 7A8AD940 ; opengl32.glBitmap
...
004303C4 7A8B48F0 ; opengl32.glRasterPos4i
004303C8 7A8B4970 ; opengl32.glRasterPos4iv
004303CC 7A8B49E0 ; opengl32.glRasterPos4s
004303D0 7A8B4A70 ; opengl32.glRasterPos4sv
004303D4 7A8B4AE0 ; opengl32.glReadBuffer
004303D8 7A8B4B50 ; opengl32.glReadPixels
004303DC 7A8B4C00 ; opengl32.glRectd
004303E0 7A8B4CB0 ; opengl32.glRectdv
004303E4 7A8B4D20 ; opengl32.glRectf
004303E8 7A8B4DE0 ; opengl32.glRectfv
004303EC 7A8B4E50 ; opengl32.glRecti
004303F0 7A8B4ED0 ; opengl32.glRectiv
004303F4 7A8B4F40 ; opengl32.glRects
; opengl32 imports range #2
004303F8 7A8B6DB0 ; opengl32.glTexParameteri
004303FC 7A8B6E30 ; opengl32.glTexParameteriv
00430400 7A8B6EB0 ; opengl32.glTexSubImage1D
00430404 7A8B6F60 ; opengl32.glTexSubImage2D
00430408 7A8B7020 ; opengl32.glTranslated
0043040C 7A8B70C0 ; opengl32.glTranslatef
00430410 7A8B7160 ; opengl32.glVertex2d
...
004304A4 7A8BB980 ; opengl32.wglGetProcAddress
004304A8 7A8B98A0 ; opengl32.wglMakeCurrent
004304AC 7A8BB2A0 ; opengl32.wglRealizeLayerPalette
004304B0 00000000
004304B4 00000000
--- snip ---
Since import-by-ordinal mechanics are not used for resolving as explained
above, misordered or even missing functions are a problem. The custom imports
resolver relies on the fact that all needed API functions appear in a specific
order in these ranges.
To investigate the discrepancy you basically use Wine's 'winedump' tool to dump
exports from Wine builtin and native opengl32. Grep out each range, by using
the start function name (see range #1 and range #2) and the count (-A argument
in grep) to generate a range "diff".
aaaaand the offender is: 'wglGetDefaultProcAddress'
It seems AJ's commit
https://source.winehq.org/git/wine.git/commitdiff/17dffaac7d5a8fc595775d456bb85e343d5aa195
("opengl32: Get WGL function definitions from the XML files.") in October 2017
removed it.
--- snip ---
-@ stub wglGetDefaultProcAddress
--- snip ---
BTW. the demo still crashes if you re-add the stub again. That's bug 43638
("Yermom demoscene demo crashes on start up") for you ;-)
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