https://bugs.winehq.org/show_bug.cgi?id=48898
Anastasius Focht focht@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@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/17dffaac7d5a8fc595775d456b... ("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