[Bug 44650] New: Multiple Blizzard games need dxgi and d3d11 dlls mapped without hole between two LOAD segments (Diablo III v2. 6. 1. 49286+, World of Warcraft, Overwatch)

wine-bugs at winehq.org wine-bugs at winehq.org
Sun Mar 4 07:24:48 CST 2018


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

            Bug ID: 44650
           Summary: Multiple Blizzard games need dxgi and d3d11 dlls
                    mapped without hole between two LOAD segments (Diablo
                    III v2. 6. 1. 49286+, World of Warcraft, Overwatch)
           Product: Wine
           Version: 3.3
          Hardware: x86-64
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P2
         Component: ntdll
          Assignee: wine-bugs at winehq.org
          Reporter: focht at gmx.net
      Distribution: ---

Hello folks,

this is mentioned in bug 40479 (first in
https://bugs.winehq.org/show_bug.cgi?id=40479#c9 with some hack) but was never
properly explained to a wider audience.

Bug 40479 is already horribly messed up with various issues mixed in and
end-user support/discussion hence I'm tracking this interesting problem here to
discuss a proper solution.

The inaccessible memory "hole" is by design.

Relevant trace log:

--- snip ---
$ pwd
/home/focht/wine-games/wineprefix64-bnet/drive_c/Program Files (x86)/Overwatch

$ WINEDEBUG=+seh,+loaddll,+relay,+ntdll,+virtual wine64 ./Overwatch.exe
>>log.txt 2>&1
...
0030:trace:module:get_load_order looking for L"C:\\windows\\system32\\dxgi.dll"
0030:trace:module:get_load_order got hardcoded default for L"dxgi.dll"
0030:trace:module:load_dll L"C:\\windows\\system32\\dxgi.dll" is a fake Wine
dll
0030:trace:module:load_builtin_dll Trying built-in L"dxgi.dll"
0030:trace:virtual:virtual_create_builtin_view created
0x7fc4ad700000-0x7fc4ad938000
0030:trace:virtual:VIRTUAL_DumpView View: 0x7fc4ad700000 - 0x7fc4ad937fff
(builtin image)
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad700000 - 0x7fc4ad700fff c-r--
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad701000 - 0x7fc4ad936fff c-r-x
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad937000 - 0x7fc4ad937fff c-rw-
...
0030:trace:virtual:NtProtectVirtualMemory 0xffffffffffffffff 0x7fc4ad937000
00001000 00000008
0030:trace:virtual:mprotect_exec forcing exec permission on
0x7fc4ad937000-0x7fc4ad937fff
0030:trace:virtual:VIRTUAL_DumpView View: 0x7fc4ad700000 - 0x7fc4ad937fff
(builtin image)
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad700000 - 0x7fc4ad700fff c-r--
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad701000 - 0x7fc4ad936fff c-r-x
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad937000 - 0x7fc4ad937fff c-rW-
0030:trace:module:load_builtin_callback loaded dxgi.dll 0x65680 0x7fc4ad700000
0030:trace:module:load_dll Loaded module L"C:\\windows\\system32\\dxgi.dll"
(builtin) at 0x7fc4ad700000
0030:trace:virtual:NtProtectVirtualMemory 0xffffffffffffffff 0x7fc4adc01d98
00000018 00000004
0030:trace:virtual:mprotect_exec forcing exec permission on
0x7fc4adc01000-0x7fc4adc01fff
0030:trace:virtual:VIRTUAL_DumpView View: 0x7fc4ad970000 - 0x7fc4adc03fff
(builtin image)
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad970000 - 0x7fc4ad970fff c-r--
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4ad971000 - 0x7fc4adc00fff c-r-x
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4adc01000 - 0x7fc4adc01fff c-rW-
0030:trace:virtual:VIRTUAL_DumpView       0x7fc4adc02000 - 0x7fc4adc03fff c-rw-
0030:trace:imports:import_dll --- CreateDXGIFactory1 dxgi.dll.2 =
0x7fc4ad710440
0030:trace:imports:import_dll --- DXGID3D10CreateDevice dxgi.dll.4 =
0x7fc4ad71047c
0030:trace:imports:import_dll --- DXGID3D10RegisterLayers dxgi.dll.5 =
0x7fc4ad7104a0
...
0030:Call KERNEL32.VirtualAlloc(00000000,00238000,00001000,00000004,)
ret=14001f538
0030:trace:virtual:NtAllocateVirtualMemory 0xffffffffffffffff (nil) 00238000
1000 00000004
0030:trace:virtual:map_view got mem in reserved area 0x3690000-0x38c8000
0030:trace:virtual:create_view forcing exec permission on 0x3690000-0x38c7fff
0030:trace:virtual:VIRTUAL_DumpView View: 0x3690000 - 0x38c7fff (valloc)
0030:trace:virtual:VIRTUAL_DumpView       0x3690000 - 0x38c7fff c-rw-
0030:Ret  KERNEL32.VirtualAlloc() retval=03690000 ret=14001f538
0030:trace:seh:NtRaiseException code=c0000005 flags=0 addr=0x14001f5fa
ip=14001f5fa tid=0030
0030:trace:seh:NtRaiseException  info[0]=0000000000000000
0030:trace:seh:NtRaiseException  info[1]=00007fc4ad737000
0030:trace:seh:NtRaiseException  rax=00000000079e4455 rbx=00007fc4ad700040
rcx=0000000000201000 rdx=000000007bdb5210
0030:trace:seh:NtRaiseException  rsi=00007fc4ad737000 rdi=00000000036c7000
rbp=00000000002360a0 rsp=0000000000235fa0
0030:trace:seh:NtRaiseException   r8=0000000003690000  r9=000000014001f538
r10=0000000000000000 r11=0000000000000246
0030:trace:seh:NtRaiseException  r12=0000000000000014 r13=000000007ffe0030
r14=000000007b472568 r15=0000000000065680
...
--- snip ---

Overwatch/WoW make private in-memory copies of 'dxgi.dll' and 'd3d11.dll' as
early as possible (loader notifications). This is done as another anti-cheat
measure/method to detect hooking/hotpatching/proxying of DirectX interfaces at
runtime by comparing real API entry points with the copies.

It assumes that all parts of the in-memory image are readable but it's not due
to the way the static and dynamic linker work.

Relevant 'strace' part, annotated.

--- snip ---
$ strace -e trace=mmap,mprotect,munmap,open -o strace.log wine64
./Overwatch.exe
...
open("/home/focht/projects/wine/wine.repo/install/bin/../lib64/wine/dxgi.dll.so",
O_RDONLY|O_CLOEXEC) = 11

; --- map first "LOAD" segment (.text, ...)
mmap(NULL, 2391592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 11, 0) =
0x7f195c3e3000

; --- make alignment "hole" inaccessible
mprotect(0x7f195c42a000, 2093056, PROT_NONE) = 0

; --- map second "LOAD" segment (.data, .bss, ...)
mmap(0x7f195c629000, 8192, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 11, 0x46000) = 0x7f195c629000

; --- .got.plt .data sections within segment
mprotect(0x7f195c3e3000, 290816, PROT_READ|PROT_WRITE) = 0   
mprotect(0x7f195c3e3000, 290816, PROT_READ|PROT_EXEC) = 0
mprotect(0x7f195c629000, 4096, PROT_READ) = 0

; --- remaining zero-filled sections (e.g. .bss)
mmap(0x7f195c400000, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f195c400000
...

; --- Page fault due to game code accessing the "hole".
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7f195c42a000} ---
...
--- snip ---

Dumping the ELF headers:

--- snip ---
$ readelf -a install/lib64/wine/dxgi.dll.so
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x20320
  Start of program headers:          64 (bytes into file)
  Start of section headers:          693488 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         7
  Size of section headers:           64 (bytes)
  Number of section headers:         35
  Section header string table index: 32
...
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.build-i NOTE             00000000000001c8  000001c8
       0000000000000024  0000000000000000   A       0     0     4
  [ 2] .gnu.hash         GNU_HASH         00000000000001f0  000001f0
       0000000000000044  0000000000000000   A       3     0     8
  [ 3] .dynsym           DYNSYM           0000000000000238  00000238
       0000000000000270  0000000000000018   A       4     2     8
  [ 4] .dynstr           STRTAB           00000000000004a8  000004a8
       00000000000001cb  0000000000000000   A       0     0     1
  [ 5] .gnu.version      VERSYM           0000000000000674  00000674
       0000000000000034  0000000000000002   A       3     0     2
  [ 6] .gnu.version_r    VERNEED          00000000000006a8  000006a8
       0000000000000050  0000000000000000   A       4     2     8
  [ 7] .rela.dyn         RELA             00000000000006f8  000006f8
       000000000000eb98  0000000000000018   A       3     0     8
  [ 8] .rela.plt         RELA             000000000000f290  0000f290
       0000000000000030  0000000000000018  AI       3    10     8
  [ 9] .init             PROGBITS         000000000000f2c0  0000f2c0
       0000000000011024  0000000000000000  AX       0     0     4
  [10] .plt              PROGBITS         00000000000202f0  000202f0
       0000000000000030  0000000000000010  AX       0     0     16
  [11] .text             PROGBITS         0000000000020320  00020320
       00000000000149fd  0000000000000000  AX       0     0     16
  [12] .fini             PROGBITS         0000000000034d20  00034d20
       0000000000000009  0000000000000000  AX       0     0     4
  [13] .rodata           PROGBITS         0000000000034d40  00034d40
       000000000000bdb0  0000000000000000   A       0     0     32
  [14] .eh_frame_hdr     PROGBITS         0000000000040af0  00040af0
       0000000000000974  0000000000000000   A       0     0     4
  [15] .eh_frame         PROGBITS         0000000000041468  00041468
       000000000000556c  0000000000000000   A       0     0     8
  [16] .init_array       INIT_ARRAY       0000000000246dc8  00046dc8
       0000000000000008  0000000000000000  WA       0     0     8
  [17] .fini_array       FINI_ARRAY       0000000000246dd0  00046dd0
       0000000000000008  0000000000000000  WA       0     0     8
  [18] .jcr              PROGBITS         0000000000246dd8  00046dd8
       0000000000000008  0000000000000000  WA       0     0     8
  [19] .data.rel.ro      PROGBITS         0000000000246de0  00046de0
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .dynamic          DYNAMIC          0000000000246de8  00046de8
       00000000000001f0  0000000000000010  WA       4     0     8
  [21] .got              PROGBITS         0000000000246fd8  00046fd8
       0000000000000028  0000000000000008  WA       0     0     8
  [22] .got.plt          PROGBITS         0000000000247000  00047000
       0000000000000028  0000000000000008  WA       0     0     8
  [23] .data             PROGBITS         0000000000247030  00047030
       0000000000000dc0  0000000000000000  WA       0     0     16
  [24] .bss              NOBITS           0000000000247df0  00047df0
       0000000000000038  0000000000000000  WA       0     0     16
...
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000469d4 0x00000000000469d4  R E    200000
  LOAD           0x0000000000046dc8 0x0000000000246dc8 0x0000000000246dc8
                 0x0000000000001028 0x0000000000001060  RW     200000
  DYNAMIC        0x0000000000046de8 0x0000000000246de8 0x0000000000246de8
                 0x00000000000001f0 0x00000000000001f0  RW     8
  NOTE           0x00000000000001c8 0x00000000000001c8 0x00000000000001c8
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_EH_FRAME   0x0000000000040af0 0x0000000000040af0 0x0000000000040af0
                 0x0000000000000974 0x0000000000000974  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000046dc8 0x0000000000246dc8 0x0000000000246dc8
                 0x0000000000000238 0x0000000000000238  R      1

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version
.gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr
.eh_fr
ame
   01     .init_array .fini_array .jcr .data.rel.ro .dynamic .got .got.plt
.data .bss
   02     .dynamic
   03     .note.gnu.build-id
   04     .eh_frame_hdr
   05
   06     .init_array .fini_array .jcr .data.rel.ro .dynamic .got
--- snip ---

* LOAD segment "00" -> align 0x200000 (x86_64 default)
* LOAD segment "01" -> align 0x200000 (x86_64 default)

ELF's "linking view":

The 0x200000 alignment of the two LOAD segments is default for static linker
'ld' on x86_64.

ELF's "execution view":

The dynamic linker/loader 'ld.so' will set the page protection of the alignment
gap between the LOAD segments to 'PROT_NONE', creating an inaccessible "hole"
by design.

https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/dl-map-segments.h;h=d36f9bd2f6938756646a876d27158d34fe3f0f0a;hb=9ace4692b2fa96c1c92177bb51e5e36680de25ad#l21

--- snip ---
  21 /* This implementation assumes (as does the corresponding implementation
  22    of _dl_unmap_segments, in dl-unmap-segments.h) that shared objects
  23    are always laid out with all segments contiguous (or with gaps
  24    between them small enough that it's preferable to reserve all whole
  25    pages inside the gaps with PROT_NONE mappings rather than permitting
  26    other use of those parts of the address space).  */
...
--- snip ---

There is a Wine-Staging patch here:

https://github.com/wine-staging/wine-staging/tree/master/patches/ntdll-Builtin_Prot

Specifically:

https://github.com/wine-staging/wine-staging/blob/master/patches/ntdll-Builtin_Prot/0001-ntdll-Fix-holes-in-ELF-mappings.patch

The patch essentially allows app code to access the memory between the LOAD
segments (alignment gaps/holes marked as 'VPROT_SYSTEM').

It's certainly doable this way ... but lets hear how Alexandre thinks of this.

An alternative to "fix" the holes is by using the static linker.
The default 'ld' setting for alignment of LOAD segments without using a custom
linker script can be adjusted by using '-z max-page-size=N' and/or '-z
common-page-size=N' (target page size).

Wine actually already makes use of this for modules which need to be
"prelinked" at fixed address ranges (core dlls):

https://source.winehq.org/git/wine.git/blob/HEAD:/tools/winegcc/winegcc.c#l1136

--- snip ---
1136     default:
1137         if (opts->image_base)
1138         {
1139             if (!try_link(opts->prefix, link_args,
"-Wl,-z,max-page-size=0x1000"))
1140                 strarray_add(link_args, "-Wl,-z,max-page-size=0x1000");
1141             if (!try_link(opts->prefix, link_args,
strmake("-Wl,-Ttext-segment=%s", opts->image_base)))
1142                 strarray_add(link_args, strmake("-Wl,-Ttext-segment=%s",
opts->image_base));
1143             else
1144                 prelink = PRELINK;
1145         }
1146         break;
1147     }
--- snip ---

Example:

https://source.winehq.org/git/wine.git/blob/HEAD:/dlls/kernel32/Makefile.in#l6

--- snip ---
   6 EXTRADLLFLAGS = -nodefaultlibs -Wb,-F,KERNEL32.dll
-Wl,--image-base,0x7b400000
--- snip ---

--- snip ---
$ readelf -a install/lib64/wine/kernel32.dll.so
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x000000007b400000 0x000000007b400000
                 0x00000000002c3bcc 0x00000000002c3bcc  R E    1000
  LOAD           0x00000000002c3dc8 0x000000007b6c4dc8 0x000000007b6c4dc8
                 0x00000000001b1448 0x00000000001b1ad0  RW     1000
--- snip ---

Due to platform (target) page granularity, there is no hole anymore in between
the two LOAD segments.

Giving 'dxgi' and 'd3d11' dlls fixed load addresses basically achieves the same
but at static link time:

--- snip ---
$ grep -Hrni image-base dlls/

dlls/dxgi/Makefile.in:4:EXTRADLLFLAGS = -Wl,--image-base,0x71710000
dlls/kernel32/Makefile.in:6:EXTRADLLFLAGS = -nodefaultlibs -Wb,-F,KERNEL32.dll
-Wl,--image-base,0x7b400000
dlls/ntdll/Makefile.in:6:EXTRADLLFLAGS = -nodefaultlibs
-Wl,--image-base,0x7bc00000
dlls/opengl32/Makefile.in:5:EXTRADLLFLAGS = -Wl,--image-base,0x7a800000
dlls/riched20/Makefile.in:4:EXTRADLLFLAGS = -Wl,--image-base,0x7ac00000
dlls/d3d11/Makefile.in:4:EXTRADLLFLAGS = -Wl,--image-base,0x77590000
--- snip ---

Alternatively, decouple the 'ld' option from Wine's 'image-base' option.

Anyway, chose between the static link time vs. runtime link time (permission
fixup -> staging patch) method ;-)

$ wine --version
wine-3.3

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