Andrey Prokopenko's Blog

Debugging game on MacOS via wine

I have experience. And patience. A man can do anything if he has those.

(Helmut Zemo)

Intro

We used to spend around half an hour in the evening by playing video games together with my son. He is a fan of both Lego and Marvel. That made LEGO Marvel game series the best candidate. Setup was rather simple: Boot Camp partition on macOS with Windows 8. We finished LEGO Marvel Super Heroes and sooner started to play the sequel. It worked until it is not. One day BSOD appeared on the screen with AppleSSD.sys error stated in it. Good luck to recover partition.

Recovery attempt

All following reboots into Boot Camp lead to Recovery menu. Ironically keyboard and touchpad drivers were not loaded at the moment making navigation impossible. With help of external keyboard and mouse I was able to navigate in recovery menu. And it led me nowhere.

What about backups? Good point. It was too old. I could probably use it, but then spend endless hours trying to restore the whole setup back again.

Alright. Okay, maybe recreate a new partition? I have made game backup and purged Boot Camp partition to realise that after all macOS upgrades I am no longer able to install Windows 8, only 11. I did not want to go down that road at all. Just give me back ability to set Windows 8 or, even better, 7.

Wine is not an emulator

Have you heard about Wine project? Wine is not an emulator. That one. The idea is simple:

  • Install wine,
  • configure underlying windows-like environment (prefix in terms of wine),
  • copy/install game inside the prefix
  • and launch it.

I have tried several attempts to use wine on different laptops in the past ten years. Most of them were unsuccessful. I had neither time nor courage or persistence to go further and understand how to debug app behaviour under wine.

This time was different. I know for sure that the game application itself was working fine on that same hardware. Speaking of which:

  • MacBook Pro 2016 year.
  • 2,9 GHz Dual-Core Intel Core i5.
  • 8 GB RAM.
  • Intel Iris Graphics 550 1536 MB.

How to install wine on macOS?

  1. Homebrew.
  2. Macports.
  3. Source code.

Homebrew offers many options (with a few false-positive results unrelated to wine):

$ brew search wine
==> Formulae
gcenx/wine/cx-llvm                       twine
gcenx/wine/game-porting-toolkit          winetricks
gcenx/wine/libinotify-kqueue

==> Casks
gcenx/wine/game-porting-toolkit          twine
gcenx/wine/portingkit                    homebrew/cask-versions/wine-devel
gcenx/wine/wine-crossover                wine-stable
gcenx/wine/wineskin                      homebrew/cask-versions/wine-staging
gcenx/wine/wineskin-devel                wire

$ brew search wine-devel
==> Casks
homebrew/cask-versions/wine-devel        wine-stable

Please note gcenx/wine cask here. It has been maintained by @Gcenx from CodeWeavers who is working on CrossOver. I have heard about CrossOver before but have not really tried it because there was a certain chance that game will not run. It was not even present in WineHQ database. I want to emphasize that it is tremendous amount of work is being done by @Gcenx. Not only Wine fork maintenance but everything around it. It was truly helpful as you can see later. (@Gcenx, if you read it, please know: this is amazing!)

I have tried almost everything from the list above without any (or almost any) prior knowledge in the domain and connected all the dots in the process. I have chosen trial’n’error approach to break choice paralysis.

  • Wineskin adds an extra layer of complexity on top of unknown in wine. Too much for beginning.
  • game-porting-toolkit provided toolkit that could be applied on top of wine. Maybe later.
  • winetricks is a tool to install missing libraries/components inside wine prefix or to modify prefix environment (Windows registry) and so on. Later.
  • wine-stable, wine-staging and wine-devel are WineHQ wine builds for macOS. Worth starting with it.
  • wine-crossover is CodeWeavers wine build based on fork adopted specifically for CrossOver. Maybe later as a last resort.

In all cases wine is under the hood. Wineskin is for managing wine prefices. You can read more about it here.

(What about Proton? Well, Proton initially had support for MacOS but Valve dropped it in favour of Linux a long time ago as it was not clear how to maintain interaction with Apple’s proprietary ecosystem. Better focus on where the most impact could be made.)

Anyway, installation is quiet simple.

brew install wine-stable winetricks

Configuration

Now since wine has been installed, give it some permissions to access certain directories and configure its prefix for the first run.

WINEPREFIX="$HOME/lego" wine winecfg

It means:

With explicitly given wine prefix which will be mounted to given directory run winecfg.exe under wine.

Under WINEPREFIX will be created dosdevices and drive_c directories. First one is for mounting different disks (C:, .., Z:). Second one will become a C: drive with Program Files, Windows, Users directories and all rest stuff needed to run basic Windows programs. Consider it as a mutable storage. It means that you possibly could use it for multiple applications but better isolate for one main app and configure separate prefix for another one. Wineskin took this idea to extreme so you can wrap and unwrap your prefices and share them across devices.

You can alter your prefix with winetricks utility which is kind of a package manager (to install specific apps or drivers) and a list of common settings (to configure mappings between windows-like environment and host machine via registry (wine regedit)).

WINEPREFIX="$HOME/lego" winetricks list-all | less

Replace with your prefix and take a look. (Also you can run it without extra commands and arguments to display GUI).

DirectX renderers

Most games are using DirectX 9, 10, 11 or 12. Those does not work with wine. Instead wine team come up with approach to support that same interface but mapping all application API calls to one of existing solutions and display app content via it, or better say, render it. There are multiple renderers present in winetricks:

WINEPREFIX=$HOME/lego winetricks list-all | grep renderer
renderer=gdi             Set renderer to gdi
renderer=gl              Set renderer to gl
renderer=no3d            Set renderer to no3d
renderer=vulkan          Set renderer to vulkan

You can use winetricks to set a renderer of your choice or you can modify registry directly, if you know the key. There is a collection of useful registry keys described in Wine wiki.

DirectX What to choose
9 OpenGL (mature choice), D9VK (Vulkan)
10 DXVK (Vulkan)
11 DXVK (Vulkan), D3DMetal
12 VKD3D (Vulkan), D3DMetal

Table 1. DirectX support.

This table could be found in many places, I add link to Wineskin as one of many sources.

Launch

At the moment when I was figuring out how to launch the game there were too many chaotic unconnected details about it so I have started with OpenGL even though the game required DirectX 11. :)

WINEPREFIX="$HOME/lego" winetricks renderer=gl
WINEPREFIX="$HOME/lego" winetricks win8

WINEPREFIX="$HOME/lego" wine 'C:\\Games\\LMSH2\\LEGOMARVEL2_DX11.exe' &> $HOME/LEGOMARVEL2_DX11.log

By the way, more on wine usage: Wine User’s Guide

OpenGL (Renderer=gl)

Almost immediately error appeared on my screen:

Figure 1. Renderer=gl
Figure 1. Renderer=gl

From log I was able to trace message to ./dlls/wined3d/context_gl.c:

GL_PRIMITIVE_RESTART_FIXED_INDEX was introduced in OpenGL 4.3. Yet according to video chip spec, it supports only OpenGL 4.1.

Moreover, with trace log level there was one more interesting insight. context_gl during initialisation encountered GL_FRAMEBUFFER_UNDEFINED while calling p_glClear function in this code:

That was so exciting. So many work to do. I was asking myself “did anyone really not deal with it?”. Sooner I have realised that OpenGL is not really suitable for DirectX 10 or 11.

Next steps for OpenGL (if I decide to go down that rabbit hole):

  • go read changelog from 4.1 to 4.3 on
    • what was changed,
    • what was added on top of 4.1,
    • what does everything mean,
    • how to bring missing parts to Wine,
  • write some tests that will fail,
  • add/modify some code,
  • re-test over and over again.

D3DMetal

Do you remember game-porting-toolkit from gcenx/wine cask? I have tried Apple’s official one in combination with wine from Homebrew cask.

Game Porting Toolkit 1.1 offers a few binaries which triggers closed-source wine64 under the hood, few builtin and native dlls and libd3dshared.dylib which provides D3DMetal Apple’s implementation of DirectX for Wine. That means that Apple are well aware about Wine and situation around it on MacOS and trying to solve it from within as they can. Anyway, their intents are great! Let’s try.

0024:trace:module:build_module loaded L"\\??\\C:\\windows\\system32\\d3d11.dll" 0000000104040CD0 0000000067D90000
0024:trace:module:load_dll Loaded module L"\\??\\C:\\windows\\system32\\d3d11.dll" at 0000000067D90000
0024:trace:module:process_attach (L"d3d11.dll",0000000000000000) - START
0024:trace:module:MODULE_InitDLL (0000000067D90000 L"d3d11.dll",PROCESS_ATTACH,0000000000000000) 0000000067D91270 - CALL
0024:trace:module:load_dll looking for L"d3d11.dll" in L"C:\\Games\\LMSH2;C:\\windows\\system32;C:\\windows\\system;C:\\windows;.;C:\\windows\\system32;C:\\windows;C:\\windows\\system32\\wbem;C:\\windows\\system32\\WindowsPowershell\\v1.0"
0024:trace:module:load_dll Found L"C:\\windows\\system32\\d3d11.dll" for L"d3d11.dll" at 0000000067D90000, count=2
0024:trace:module:LdrGetDllHandleEx flags 0x1, load_path 0000000000000000, dll_characteristics 0000000000000000, name 000000000011EE50, base 000000000011EE48.
0024:trace:module:LdrGetDllHandleEx L"ntdll.dll" -> 0000000170000000 (load path (null))

Assertion failed: (GFXTHandle && "Failed to dlopen D3DMetal"), function D3DRMDispatch_Init_block_invoke, file shared.mm, line 699.

Next steps (if I decide to go down that road):

  • go to Apple forums,
  • submit logs,
  • wait.

DXVK + renderer=vulkan

winetricks offers all dxvk builds from GitHub. And few alternatives.

WINEPREFIX="$HOME/lego" winetricks | grep dxvk
dxvk                     Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (latest) (Philip Rebohle, 2023) [downloadable]
...
dxvk1094                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (1.9.4) (Philip Rebohle, 2017) [downloadable]
dxvk1100                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (1.10) (Philip Rebohle, 2017) [downloadable]
dxvk1101                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (1.10.1) (Philip Rebohle, 2017) [downloadable]
dxvk1102                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (1.10.2) (Philip Rebohle, 2017) [downloadable]
dxvk1103                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (1.10.3) (Philip Rebohle, 2022) [downloadable,cached]
dxvk2000                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (2.0) (Philip Rebohle, 2022) [downloadable]
dxvk2010                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (2.1) (Philip Rebohle, 2023) [downloadable]
dxvk2020                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (2.2) (Philip Rebohle, 2023) [downloadable]
dxvk2030                 Vulkan-based D3D9/D3D10/D3D11 implementation for Linux / Wine (2.3) (Philip Rebohle, 2023) [downloadable]
dxvk_nvapi0061           Alternative NVAPI Vulkan implementation on top of DXVK for Linux / Wine (0.6.1) (Jens Peters, 2023) [downloadable]

Alright, install it and run the game.

WINEPREFIX="$HOME/lego" winetricks dxvk

WINEPREFIX="$HOME/lego" wine 'C:\\Games\\LMSH2\\LEGOMARVEL2_DX11.exe' &> $HOME/LEGOMARVEL2_DX11.log

And receive the same modal window with fatal error and following log entry:

err:D3D11CoreCreateDevice: Requested feature level not supported D3D_FEATURE_LEVEL_9_1

Why is it not supported? At this point I need more info from wine/DXVK logs.

wine_vkCreateDevice Device name: "Intel(R) Iris(TM) Graphics 550".
wine_vkCreateDevice Vendor ID: 0x8086, Device ID: 0x1927.
wine_vkCreateDevice Driver version: 0x277a.

Well, it turned out that video chip does not support Vulkan 1.3, yet the most recent version of DXVK obtained from winetricks supports only Vulkan 1.3 and dropped Vulkan 1.2 according to their Release notes. After downgrading DXVK to 1.10.3 (the last version that supported Vulkan 1.2) I messed up with wine prefix.

At this point I have decided to create a new prefix and try again but building DXVK from sources.

Building DXVK from sources

# prerequisites
$ brew install bison mingw-w64 glslang

# building
$ git clone --recursive https://github.com/doitsujin/dxvk.git
$ cd dxvk
$ ./package-release.sh master $(pwd)/target --dev-build

Oh, wait. I am building HEAD instead of 1.10.3. Now I need to trace 1.10.3 back, create a branch and start again. Fortunately, @Gcenx is rescuing me again! He followed that same route and is still maintaining 1.10.x branch which was exactly what I needed.

Purging dxvk directory and trying again.

  • Building:
$ git clone --recursive https://github.com/Gcenx/DXVK-macOS.git
cd DXVK-macOS
$ ./package-release.sh 1.10.x $(pwd)/target --dev-build
  • Installing:
cp $(pwd)/target/dxvk-macOS-async-1.10.x/x64/*.dll $WINEPREFIX/drive_c/windows/syswow64
cp $(pwd)/target/dxvk-macOS-async-1.10.x/x32/*.dll $WINEPREFIX/drive_c/windows/system32

Manually add overrides as on screenshot:

Figure 2. DXVK overrides
Figure 2. DXVK overrides
  • Running:
WINEPREFIX=$HOME/lego2 wine 'C:\\Games\\LMSH2\\LEGOMARVEL2_DX11.exe' > $HOME/LEGOMARVEL2_DX11.log

And getting 014c error of incompatible architectures. Game expected x64 dlls in C:\Windows\system32. Fine, feed x64 dlls to it.

cp $(pwd)/target/dxvk-macOS-async-1.10.x/x64/*.dll $WINEPREFIX/drive_c/windows/syswow64
cp $(pwd)/target/dxvk-macOS-async-1.10.x/x64/*.dll $WINEPREFIX/drive_c/windows/system32

After restart game eventually made quiet a progress: - Tray icon was rendered! - Black rectangle was displayed!!! - And that was it. Application just stuck.

err:sync:RtlpWaitForCriticalSection section 000000010055F670 (null) wait timed out in thread 0174, blocked by 0170, retrying (60 sec)

How to debug such issues?

apitrace

In DXVK wiki and Wine documentation too there is mentioned apitrace utility.

Unfortunately, there is no MacOS binary distribution. Anyway, let’s try Windows one with wine. After copying tracing libraries in directory with game and reproducing black rectangle. I have collected .apitrace file in $WINEPREFIX/drive_c/users/crossover/Desktop directory.

WINEPREFIX=lego2 wine $HOME/Downloads/apitrace-11.1-win64/bin/qapitrace.exe

It did not give me much insights. I saw 6 swap-chains, a lot of information that I did not undertand.

Figure 3. apitrace app screenshot
Figure 3. apitrace app screenshot

dxvk.conf

After looking at those swap-chain long enough, I have noticed that the last one was incomplete. What if this behaviour could be overcomed via some configuration tweaks?

# Create the VkSurface on the first call to IDXGISwapChain::Present,
# rather than when creating the swap chain. Some games that start
# rendering with a different graphics API may require this option,
# or otherwise the window may stay black.
# 
# Supported values: True, False

# dxgi.deferSurfaceCreation = False
# d3d9.deferSurfaceCreation = False

combination of SwapChain, rendering and window may stay black lead me to give it a try.

I have set

dxgi.deferSurfaceCreation = True

and added few other best practices just in case.

export DXVK_LOG_LEVEL=debug
export DXVK_LOG_PATH=$HOME
export DXVK_HUD=all

With all of that I have received following picture.

Figure 4. DXVK debug info on top of black rectangle
Figure 4. DXVK debug info on top of black rectangle

winedbg

After retrying error remained the same but thread ids were changed. Most of the times they were identical around 0180.

0184:err:sync:RtlpWaitForCriticalSection section 00000001006269B0 (null) wait timed out in thread 0184, blocked by 0180, retrying (60 sec)

Another approach is to analyse application runtime is to attach debugger to it and examine memory with debugger.

WINEPREFIX=$HOME/lego2 wine winedbg.exe
Wine-dbg>info process
 pid      threads  executable (all id:s are in hex)
 000001a8 1        'start.exe'
=000001b8 1        \_ 'winedbg.exe'
 000001b0 2        \_ 'conhost.exe'
 00000030 9        'services.exe'
 000000d8 6        \_ 'rpcss.exe'
 000000a4 3        \_ 'svchost.exe'
 0000008c 6        \_ 'plugplay.exe'
 00000060 8        \_ 'winedevice.exe'
 0000003c 7        \_ 'winedevice.exe'
 00000020 30       'LEGOMARVEL2_DX11.exe'
 000000c4 3        \_ 'explorer.exe'
Wine-dbg>attach 0x20
0x006fffffca5e21 ntdll+0x55e21: ret
Wine-dbg>bt 0x0180
Backtrace:
=>0 0x006fffffca5a64 in ntdll (+0x55a64) (0x000000fffffff4)
  1 0x006fffff4d3f70 in kernelbase (0x000000fffffff4)
  2 0x006fffff4d480e in kernelbase (0000000000000000)
  3 0x0000014054c46d in legomarvel2_dx11 (+0x54c46d) (0000000000000000)
  4 0x000001405527ab in legomarvel2_dx11 (+0x5527ab) (0000000000000000)
  5 0x000001403033dc in legomarvel2_dx11 (+0x3033dc) (0000000000000000)
  6 0x006fffffaa8c79 in kernel32 (0000000000000000)
  7 0x006fffffca5deb in ntdll (+0x55deb) (0000000000000000)
Wine-dbg>bt 0x184
Backtrace:
=>0 0x006fffffca5a04 in ntdll (+0x55a04) (0x0000011c0bf9d0)
  1 0x006fffffcacda8 in ntdll (0x0000011c0bf9d0)
  2 0x006fffffcacda8 in ntdll (0x0000011c0bf9d0)
  3 0x006fffffcacda8 in ntdll (0x0000011c0bf9d0)
  4 0x006fffffcacf46 in ntdll (0x000001006269b0)
  5 0x006fffffcacf46 in ntdll (0x000001006269b0)
  6 0x006fffffcad1b1 in ntdll (0000000000000000)
  7 0x000001402dc48d in legomarvel2_dx11 (+0x2dc48d) (0000000000000000)
  8 0x0000014054c45b in legomarvel2_dx11 (+0x54c45b) (0000000000000000)
  9 0x000001405527ab in legomarvel2_dx11 (+0x5527ab) (0000000000000000)
  10 0x000001403033dc in legomarvel2_dx11 (+0x3033dc) (0000000000000000)
  11 0x006fffffaa8c79 in kernel32 (0000000000000000)
  12 0x006fffffca5deb in ntdll (+0x55deb) (0000000000000000)

Yes, it was wine-stable compiled without debug symbols. To enable debug symbols I need to install wine-devel.

Building Wine from sources

At this point maybe better compile wine from sources? But which one? After taking a look at multiple repos, I have chosen wine-crossover (winecx) as there was written very clear installation procedure.

Although the compilation process is very famous (./configure && make && make install) the devil is hiding in many tiny details. The whole compilation pipeline lasts for more than four hours. make phase was taking the most time. While trying different options I was executing this command before going to sleep. When I was iterating over some hypotheses, I would stick to recompilation only particular DLLs instead of rebuilding everything from scratch. But in some cases I have corrupted wine prefix and it was necessary to make clean install of everything.

Few prerequisites to enable Vulkan support in Wine:

brew install bison mingw-w64 lcms2 FAudio sdl2 gstreamer gst-plugins-base gst-plugins-good vulkan-headers molten-vk

Next step is ./configure:

export SDL2_CFLAGS="-I/usr/local/Cellar/sdl2/2.30.0/include/SDL2 -D_THREAD_SAFE"
export SDL2_LIBS="-L/usr/local/Cellar/sdl2/2.30.0/lib -lSDL2"
export DYLD_LIBRARY_PATH="/usr/local/lib:/usr/local/Cellar/molten-vk/1.2.7/lib:$DYLD_LIBRARY_PATH"
export PCAP_CFLAGS="-I/usr/local/opt/libpcap/include"
export PCAP_LIBS="-L/usr/local/Cellar/libpcap/1.10.4/lib -lpcap"

./configure --disable-option-checking \
--disable-tests \
--enable-archs=i386,x86_64 \
--without-alsa \
--without-capi \
--with-coreaudio \
--with-cups \
--without-dbus \
--without-fontconfig \
--with-freetype \
--with-gettext \
--without-gettextpo \
--without-gphoto \
--with-gnutls \
--without-gssapi \
--with-gstreamer \
--without-inotify \
--without-krb5 \
--with-mingw \
--without-netapi \
--with-opencl \
--with-opengl \
--without-oss \
--with-pcap \
--with-pcsclite \
--with-pthread \
--without-pulse \
--without-sane \
--with-sdl \
--without-udev \
--with-unwind \
--without-usb \
--without-v4l2 \
--with-vulkan \
--without-wayland \
--without-x

Then comes make. First run will probably fail with following error:

distversion.h: No such file or directory

To fix it we’ll provide distversion.h from here:

cat distversion.patch
diff -uNr sources.orig/wine/include/distversion.h sources/wine/include/distversion.h
--- sources.orig/wine/include/distversion.h	1970-01-01 09:00:00.000000000 +0900
+++ sources/wine/include/distversion.h	2021-02-07 17:03:44.000000000 +0900
@@ -0,0 +1,12 @@
+/* ---------------------------------------------------------------
+*   distversion.c
+*
+* Copyright 2013, CodeWeavers, Inc.
+*
+* Information from DISTVERSION which needs to find
+* its way into the wine tree.
+* --------------------------------------------------------------- */
+
+#define WINDEBUG_WHAT_HAPPENED_MESSAGE "This can be caused by a problem in the program or a deficiency in Wine. You may want to check <a href=\"http://www.codeweavers.com/compatibility/\">http://www.codeweavers.com/compatibility/</a> for tips about running this application."
+
+#define WINDEBUG_USER_SUGGESTION_MESSAGE "If this problem is not present under Windows and has not been reported yet, you can save the detailed information to a file using the \"Save As\" button, then <a href=\"http://www.codeweavers.com/support/tickets/enter/\">file a bug report</a> and attach that file to the report."

patch -u programs/winedbg/distversion.h -i distversion.patch

After few hours make succeeded.

sudo make install

It will take another half an hour. Then launch the game again to receive new error in the log:

err:vulkan:get_vulkan_driver Wine was built without Vulkan support.

Wait, what? I remember explicitly mentioning --with-vulkan option. Trace error back to source file:

I did remember checking libVulkan during configuration. After tracing back SONAME_LIBMOLTENVK I found the following logic in configure.ac:

In other words, “if libvulkan is not present check for libmoltenvk, otherwise, skip libmoltenvk”. I removed that condition and tried to recompile Wine again.

There were issues with symbolic links so I have removed vulkan-headers and molten-vk by brew and decided to build MoltenVK from sources.

brew uninstall vulkan-headers molten-vk
brew cleanup

Building MoltenVK from sources

Some prerequisites:

brew install cmake python3 ninja

Then clone repo and run installation process:

git clone https://github.com/KhronosGroup/MoltenVK.git
cd MoltenVK
./fetchDependencies --macos # this step takes a couple hours
make macos-debug
sudo make install

In the beginning I have built everything. It produced libraries not only for MacOS but for iOS too. After messing up with symlinks I have received following weird error during ./configure:

configure:19929: checking for -lMoltenVK
configure:19958: gcc -m64 -o conftest -g -O2 -DIS_WOW64_BUILD -I/usr/local/opt/libpcap/include -L/usr/local/opt/libpcap/lib conftest.c -lMoltenVK   >&5
ld: warning: ignoring file /usr/local/lib/libMoltenVK.dylib, building for macOS-x86_64 but attempting to link with file built for unknown-unsupported file format ( 0xCF 0xFA 0xED 0xFE 0x07 0x00 0x00 0x01 0x03 0x00 0x00 0x00 0x0A 0x00 0x00 0x00 )
Undefined symbols for architecture x86_64:
  "_vkGetInstanceProcAddr", referenced from:
      _main in conftest-86360e.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
configure:19958: $? = 1
configure: failed program was:
| /* confdefs.h */
| #define PACKAGE_NAME "Wine"
| #define PACKAGE_TARNAME "wine"
| #define PACKAGE_VERSION "9.0"
| #define PACKAGE_STRING "Wine 9.0"
| #define PACKAGE_BUGREPORT "wine-devel@winehq.org"
| #define PACKAGE_URL "https://www.winehq.org"
| #define EXEEXT ""
| #define HAVE_STDIO_H 1
| #define HAVE_STDLIB_H 1
| #define HAVE_STRING_H 1
| #define HAVE_INTTYPES_H 1
| #define HAVE_STDINT_H 1
| #define HAVE_STRINGS_H 1
| #define HAVE_SYS_STAT_H 1
| #define HAVE_SYS_TYPES_H 1
| #define HAVE_UNISTD_H 1
| #define STDC_HEADERS 1
| #define HAVE_IOKIT_USB_IOUSBLIB_H 1
| #define HAVE_OPENCL_OPENCL_H 1
| #define HAVE_ARPA_INET_H 1
| #define HAVE_ARPA_NAMESER_H 1
| #define HAVE_FLOAT_H 1
| #define HAVE_LIBPROC_H 1
| #define HAVE_MACH_O_LOADER_H 1
| #define HAVE_MACH_MACH_H 1
| #define HAVE_NETDB_H 1
| #define HAVE_NETINET_IN_H 1
| #define HAVE_NETINET_IN_SYSTM_H 1
| #define HAVE_NETINET_TCP_H 1
| #define HAVE_NETINET_TCP_FSM_H 1
| #define HAVE_PCAP_PCAP_H 1
| #define HAVE_PWD_H 1
| #define HAVE_SCHED_H 1
| #define HAVE_STDINT_H 1
| #define HAVE_SYS_ATTR_H 1
| #define HAVE_SYS_EVENT_H 1
| #define HAVE_SYS_FILIO_H 1
| #define HAVE_SYS_IPC_H 1
| #define HAVE_SYS_PARAM_H 1
| #define HAVE_SYS_PTRACE_H 1
| #define HAVE_SYS_QUEUE_H 1
| #define HAVE_SYS_RANDOM_H 1
| #define HAVE_SYS_RESOURCE_H 1
| #define HAVE_SYS_SHM_H 1
| #define HAVE_SYS_SIGNAL_H 1
| #define HAVE_SYS_SOCKETVAR_H 1
| #define HAVE_SYS_SOCKIO_H 1
| #define HAVE_SYS_STATVFS_H 1
| #define HAVE_SYS_SYSCALL_H 1
| #define HAVE_SYS_TIMES_H 1
| #define HAVE_SYS_UIO_H 1
| #define HAVE_SYS_USER_H 1
| #define HAVE_SYS_UTSNAME_H 1
| #define HAVE_SYS_VNODE_H 1
| #define HAVE_SYS_XATTR_H 1
| #define HAVE_UTIME_H 1
| #define HAVE_SYS_CONF_H 1
| #define HAVE_SYS_MOUNT_H 1
| #define HAVE_SYS_USER_H 1
| #define HAVE_SYS_SYSCTL_H 1
| #define HAVE_NETINET_IP_H 1
| #define HAVE_NET_IF_H 1
| #define HAVE_NET_IF_ARP_H 1
| #define HAVE_NET_IF_DL_H 1
| #define HAVE_NET_IF_TYPES_H 1
| #define HAVE_NET_ROUTE_H 1
| #define HAVE_NETINET_IF_ETHER_H 1
| #define HAVE_NETINET_IN_PCB_H 1
| #define HAVE_NETINET_IP_ICMP_H 1
| #define HAVE_NETINET_IP_VAR_H 1
| #define HAVE_NETINET_UDP_H 1
| #define HAVE_SYS_UN_H 1
| #define HAVE_NETINET_UDP_VAR_H 1
| #define HAVE_NETINET_ICMP_VAR_H 1
| #define HAVE_NETINET_ICMP6_H 1
| #define HAVE_NETINET_TCP_VAR_H 1
| #define HAVE_RESOLV_H 1
| #define HAVE_IFADDRS_H 1
| #define HAVE_SYS_UCONTEXT_H 1
| #define XATTR_ADDITIONAL_OPTIONS 1
| #define HAVE_AVFOUNDATION_AVFOUNDATION_H 1
| #define HAVE_IOSERVICEMATCHING 1
| #define HAVE_MTLDEVICE_REGISTRYID 1
| #define X_DISPLAY_MISSING 1
| #define SONAME_LIBGNUTLS "libgnutls.30.dylib"
| #define HAVE_GNUTLS_CIPHER_INIT 1
| #define HAVE_RESOLV 1
| #define HAVE_RES_GETSERVERS 1
| #define HAVE_FT2BUILD_H 1
| #define SONAME_LIBFREETYPE "libfreetype.6.dylib"
| #define HAVE_FREETYPE 1
| #define HAVE_FT_TRUETYPEENGINETYPE 1
| #define HAVE_LIBUNWIND 1
| #define HAVE_SDL_H 1
| #define SONAME_LIBSDL2 "libSDL2-2.0.0.dylib"
| #define HAVE_CUPS_CUPS_H 1
| #define HAVE_CUPS_PPD_H 1
| #define SONAME_LIBCUPS "libcups.2.dylib"
| #define SONAME_LIBODBC "libodbc.2.dylib"
| /* end confdefs.h.  */
| 
| /* Override any GCC internal prototype to avoid an error.
|    Use char because int might match the return type of a GCC
|    builtin and then its argument prototype would still apply.

Of course, macOS build was expected, not iOS one. Pointing out location to proper debug library $(pwd)/MoltenVK/dylib/macOS/libMoltenVK.dylib helped.

After Wine recompilation I got following error:

_mtlValidateStrideTextureParameters:1824: failed assertion `Texture Descriptor Validation

It was a known MoltenVK issue from upstream. On the very next day I discovered that PR #2169 is merged. So I pulled changes, recompiled MoltenVK and tried again. But the issue persisted. I took a look at GitHub:

  • People were working hard on a feature called native texture atomics.
  • It supposed to be merged in next MotlenVK release v.1.2.8.
  • Feature introduced a regression which I had encountered.
  • I’ve checked out 37361ccd that was related to v.1.2.7 release and rebuilt everything again one more time.

After that I have recompiled Wine again (just in case) and finally reproduced CriticalSection error!

0180:err:sync:RtlpWaitForCriticalSection section 0000000100626FF0 (null) wait timed out in thread 0180, blocked by 017c, retrying (60 sec)

Root cause analysis

Let’s revisit back traces again.

Wine-dbg>bt 0x0180
Backtrace:
=>0 0x006fffffca5a04 in ntdll (+0x55a04) (0x0000011c0bf9d0)
  1 0x006fffffcacda8 _InterlockedCompareExchange(addr=0000000100627008, cmp=00006FFFFFCCBEB0, size=<internal error>, timeout=000000011C0BF9D0) [$WINECX/include/winnt.h:7005] in ntdll (0
x0000011c0bf9d0)
  2 0x006fffffcacda8 spin_lock(addr=<register R12 not accessible in this frame>, cmp=<register R13 not accessible in this frame>, size=<internal error>, timeout=<register RBP not accessible in this frame>
) [$WINECX/dlls/ntdll/sync.c:912] in ntdll (0x0000011c0bf9d0)
  3 0x006fffffcacda8 RtlWaitOnAddress+0x168(addr=<register R12 not accessible in this frame>, cmp=<register R13 not accessible in this frame>, size=<internal error>, timeout=<register RBP not accessible i
n this frame>) [$WINECX/dlls/ntdll/sync.c:912] in ntdll (0x0000011c0bf9d0)
  4 0x006fffffcacf46 wait_semaphore+0x65(timeout=<internal error>, crit=<internal error>) [$WINECX/dlls/ntdll/sync.c:196] in ntdll (0x00000100626ff0)
  5 0x006fffffcacf46 RtlpWaitForCriticalSection+0x76(crit=<register RBP not accessible in this frame>) [$WINECX/dlls/ntdll/sync.c:305] in ntdll (0x00000100626ff0)
  6 0x006fffffcad1b1 RtlEnterCriticalSection+0x91(crit=<register RBX not accessible in this frame>) [$WINECX/dlls/ntdll/sync.c:383] in ntdll (0000000000000000)
  7 0x000001402dc48d in legomarvel2_dx11 (+0x2dc48d) (0000000000000000)
  8 0x0000014054c45b in legomarvel2_dx11 (+0x54c45b) (0000000000000000)
  9 0x000001405527ab in legomarvel2_dx11 (+0x5527ab) (0000000000000000)
  10 0x000001403033dc in legomarvel2_dx11 (+0x3033dc) (0000000000000000)
  11 0x006fffffaa8c79 BaseThreadInitThunk+0x9(unknown=<internal error>, entry=<internal error>, arg=<internal error>) [$WINECX/dlls/kernel32/thread.c:61] in kernel32 (0000000000000000)
  12 0x006fffffca5deb in ntdll (+0x55deb) (0000000000000000)
Wine-dbg>bt 0x017c
Backtrace:
=>0 0x006fffffca5a64 in ntdll (+0x55a64) (0x000000fffffff4)
  1 0x006fffff4d3f70 WaitForMultipleObjectsEx+0xc8(count=0x1, handles=000000011BEBFA80, wait_all=0, timeout=<internal error>, alertable=0) [$WINECX/dlls/kernelbase/sync.c:383] in kernel
base (0x000000fffffff4)
  2 0x006fffff4d480e WaitForSingleObject+0x26(handle=<is not available>, timeout=<internal error>) [$WINECX/dlls/kernelbase/sync.c:339] in kernelbase (0000000000000000)
  3 0x0000014054c46d in legomarvel2_dx11 (+0x54c46d) (0000000000000000)
  4 0x000001405527ab in legomarvel2_dx11 (+0x5527ab) (0000000000000000)
  5 0x000001403033dc in legomarvel2_dx11 (+0x3033dc) (0000000000000000)
  6 0x006fffffaa8c79 BaseThreadInitThunk+0x9(unknown=<internal error>, entry=<internal error>, arg=<internal error>) [$WINECX/dlls/kernel32/thread.c:61] in kernel32 (0000000000000000)
  7 0x006fffffca5deb in ntdll (+0x55deb) (0000000000000000)

Thread 0180 is waiting for 017c, thread 017c is waiting for something else. I definitely needed more info. So decided to look at the logging levels more closely. Debug Logging wiki article contains more on how to configure logging. I have enabled trace+all and restarted again. Wine produced 1.6 GB of logs.

compstui.dll

While navigating inside log file I’ve focused on thread that was both locked by something else and locking some other thread.

1220316.497:0180:trace:module:alloc_thread_tls slot 0: 327716/0 bytes at 000000010078F2A0
1220316.497:0180:trace:module:alloc_thread_tls slot 1: 8/0 bytes at 000000010037BA00
1220316.497:0180:trace:module:alloc_thread_tls slot 2: 8/0 bytes at 000000010037BA30
1220316.497:0180:Call PE DLL (proc=00006FFFFF02CC30,module=00006FFFFEFC0000 L"msvcrt.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.497:0180:trace:msvcrt:DllMain (00006FFFFEFC0000, DLL_THREAD_ATTACH, 0000000000000000) pid(20), tid(180), tls(64)
1220316.497:0180:Ret  PE DLL (proc=00006FFFFF02CC30,module=00006FFFFEFC0000 L"msvcrt.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.497:0180:Call PE DLL (proc=00006FFFFEB76510,module=00006FFFFEAF0000 L"ucrtbase.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.497:0180:trace:msvcrt:DllMain (00006FFFFEAF0000, DLL_THREAD_ATTACH, 0000000000000000) pid(20), tid(180), tls(65)
1220316.497:0180:Call ntdll.RtlFlsSetValue(00000001,fffffffffffffffe) ret=6fffff4daa63
1220316.497:0180:Ret  ntdll.RtlFlsSetValue() retval=00000000 ret=6fffff4daa63
1220316.497:0180:Ret  PE DLL (proc=00006FFFFEB76510,module=00006FFFFEAF0000 L"ucrtbase.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.497:0180:Call PE DLL (proc=00006FFFFDD1B020,module=00006FFFFDC90000 L"user32.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.497:0180:Ret  PE DLL (proc=00006FFFFDD1B020,module=00006FFFFDC90000 L"user32.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.497:0180:Call PE DLL (proc=00006FFFFD8DD410,module=00006FFFFD8D0000 L"imm32.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.497:0180:trace:imm:DllMain instance 00006FFFFD8D0000, reason 2, reserved 0000000000000000
1220316.497:0180:Ret  PE DLL (proc=00006FFFFD8DD410,module=00006FFFFD8D0000 L"imm32.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.497:0180:Call PE DLL (proc=00006FFFFEA432D0,module=00006FFFFEA30000 L"WS2_32.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.497:0180:Ret  PE DLL (proc=00006FFFFEA432D0,module=00006FFFFEA30000 L"WS2_32.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.497:0180:Call PE DLL (proc=00006FFFFE7DADC0,module=00006FFFFE790000 L"rpcrt4.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.497:0180:Ret  PE DLL (proc=00006FFFFE7DADC0,module=00006FFFFE790000 L"rpcrt4.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.498:0180:Call PE DLL (proc=00006FFFFBC65F10,module=00006FFFFBC40000 L"combase.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.498:0180:trace:ole:DllMain 00006FFFFBC40000, 0x2, 0000000000000000
1220316.498:0180:Ret  PE DLL (proc=00006FFFFBC65F10,module=00006FFFFBC40000 L"combase.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.498:0180:Call PE DLL (proc=00006FFFFBE95900,module=00006FFFFBDF0000 L"ole32.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.498:0180:trace:ole:DllMain 00006FFFFBDF0000, 0x2, 0000000000000000.
1220316.498:0180:Ret  PE DLL (proc=00006FFFFBE95900,module=00006FFFFBDF0000 L"ole32.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.498:0180:Call PE DLL (proc=00006FFFFA930610,module=00006FFFFA8F0000 L"WININET.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.498:0180:Ret  PE DLL (proc=00006FFFFA930610,module=00006FFFFA8F0000 L"WININET.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.498:0180:Call TLS callback (proc=00006FFFF83E3130,module=00006FFFF8350000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Ret  TLS callback (proc=00006FFFF83E3130,module=00006FFFF8350000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Call TLS callback (proc=00006FFFF83E3100,module=00006FFFF8350000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Ret  TLS callback (proc=00006FFFF83E3100,module=00006FFFF8350000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Call TLS callback (proc=00006FFFF83F0270,module=00006FFFF8350000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Ret  TLS callback (proc=00006FFFF83F0270,module=00006FFFF8350000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Call PE DLL (proc=00006FFFF8351330,module=00006FFFF8350000 L"dxgi.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.498:0180:Ret  PE DLL (proc=00006FFFF8351330,module=00006FFFF8350000 L"dxgi.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.498:0180:Call TLS callback (proc=00006FFFF90C9C20,module=00006FFFF8FA0000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Ret  TLS callback (proc=00006FFFF90C9C20,module=00006FFFF8FA0000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Call TLS callback (proc=00006FFFF90C9BF0,module=00006FFFF8FA0000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Ret  TLS callback (proc=00006FFFF90C9BF0,module=00006FFFF8FA0000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Call TLS callback (proc=00006FFFF90D6C40,module=00006FFFF8FA0000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Ret  TLS callback (proc=00006FFFF90D6C40,module=00006FFFF8FA0000,reason=THREAD_ATTACH,reserved=0)
1220316.498:0180:Call PE DLL (proc=00006FFFF8FA1330,module=00006FFFF8FA0000 L"d3d11.dll",reason=THREAD_ATTACH,res=0000000000000000)
1220316.498:0180:Ret  PE DLL (proc=00006FFFF8FA1330,module=00006FFFF8FA0000 L"d3d11.dll",reason=THREAD_ATTACH,res=0000000000000000) retval=1
1220316.498:0180:Call PE DLL (proc=00006FFFF7F22270,module=00006FFFF7F20000 L"compstui.dll",reason=THREAD_ATTACH,res=0000000000000000)

It has called compstui.dll (message started with Call) but never received anything back (no Ret). In ./dlls/compstui/comsptui_main.c there was an extra log channel called compstui:

I have added it to WINEDEBUG, restarted the game to realise that log was corrupted.

Figure 5. compstui log (left)
Figure 5. compstui log (left)

It turned out that winecx (CrossOver fork of Wine) has missed to port MR !1174 back from upstream.

After cherry-picking it and back-porting to winecx compstui did not throw more errors but it did not solve CriticalSection issue.

Hypothesis: thread renaming

I have noticed that Critical Section locks were happening around thread renaming:

  • After wine context initialisation game Main thread 0024 started.
  • It started to create more worker threads for game specific reasons.
  • Those threads were having names, i.e. game main thread renamed its child threads.
  • It turned out that unlike in Windows it is not possible to rename one thread from another one in MacOS.
  • In order to ensure that this renaming event happening, wine is raising renaming event via structured exception handler (SEH).
  • Child thread is receiving that event and performs renaming by itself.
  • Moreover, thread name was truncated to 31 characters due to Apple limitations on thread name length.
  • The more I looked into it, the more I suspected that renaming is somehow related to the problem if not causing it.
  • And game got stuck on shader worker threads.

Here is excerpt from logs (warn log level, threadname log channel):

1220306.025:0038:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_server"
1220306.119:0044:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220306.122:0048:warn:threadname:NtSetInformationThread Thread renamed to L"wine_threadpool_worker"
1220306.138:0050:warn:threadname:NtSetInformationThread Thread renamed to L"wine_sechost_service"
1220306.248:0068:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220306.264:0070:warn:threadname:NtSetInformationThread Thread renamed to L"wine_sechost_service"
1220307.101:0090:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220307.115:0098:warn:threadname:NtSetInformationThread Thread renamed to L"wine_sechost_service"
1220307.117:009c:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_server"
1220307.173:00a8:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220307.188:00b0:warn:threadname:NtSetInformationThread Thread renamed to L"wine_sechost_service"
1220310.943:00cc:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_server"
1220310.955:00d0:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220311.014:00dc:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220311.018:00e0:warn:threadname:NtSetInformationThread Thread renamed to L"wine_threadpool_worker"
1220311.030:00e8:warn:threadname:NtSetInformationThread Thread renamed to L"wine_sechost_service"
1220311.033:00ec:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_server"
1220311.034:00f0:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_server"
1220311.049:00f4:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220311.052:00f8:warn:threadname:NtSetInformationThread Thread renamed to L"wine_threadpool_worker"
1220313.291:0024:warn:threadname:dispatch_exception Thread ID 0000 renamed to ""
1220313.292:0024:warn:threadname:dispatch_exception Thread ID 0000 renamed to "Main"
1220313.354:0024:warn:threadname:dispatch_exception Thread ID 0000 renamed to ""
1220313.355:0024:warn:threadname:dispatch_exception Thread ID 0000 renamed to "Main"
1220314.714:0104:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220314.714:010c:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-submit"
1220314.714:0108:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220314.714:00fc:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220314.714:0100:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220314.715:0110:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-queue"
1220314.728:0114:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-cs"
1220315.160:0118:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.162:011c:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.164:0120:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.165:0124:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.167:0128:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-submit"
1220315.169:012c:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-queue"
1220315.174:0130:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-cs"
1220315.797:0134:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.798:0140:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.800:013c:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.801:0138:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-pcompiler"
1220315.808:0144:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-submit"
1220315.809:0148:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-queue"
1220315.817:014c:warn:threadname:NtSetInformationThread Thread renamed to L"dxvk-cs"
1220316.227:0024:warn:threadname:dispatch_exception Thread ID 0150 renamed to "NuBgProc-ET-Sequential"
1220316.228:0024:warn:threadname:dispatch_exception Thread ID 0154 renamed to "NuBgProc-ET-Long"
1220316.229:0024:warn:threadname:dispatch_exception Thread ID 0158 renamed to "NuBgProc-ET-Long"
1220316.229:0150:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Sequential"
1220316.230:0024:warn:threadname:dispatch_exception Thread ID 015c renamed to "NuBgProc-ET-Long"
1220316.230:0024:warn:threadname:dispatch_exception Thread ID 0160 renamed to "NuBgProc-ET-Short"
1220316.230:0154:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Long"
1220316.231:0024:warn:threadname:dispatch_exception Thread ID 0164 renamed to "NuBgProc-ET-Short"
1220316.231:0024:warn:threadname:dispatch_exception Thread ID 0168 renamed to "NuBgProc-ET-Short"
1220316.231:0158:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Long"
1220316.232:015c:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Long"
1220316.233:0160:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Short"
1220316.234:0164:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Short"
1220316.235:0168:warn:threadname:dispatch_exception Thread renamed to "NuBgProc-ET-Short"
1220316.236:0024:warn:threadname:dispatch_exception Thread ID 016c renamed to "USBHID_THREAD"
1220316.238:016c:warn:threadname:dispatch_exception Thread renamed to "USBHID_THREAD"
1220316.245:0024:warn:threadname:dispatch_exception Thread ID 0170 renamed to "m_readThread"
1220316.246:0024:warn:threadname:dispatch_exception Thread ID 0174 renamed to "m_decodeThread"
1220316.247:0170:warn:threadname:dispatch_exception Thread renamed to "m_readThread"
1220316.248:0174:warn:threadname:dispatch_exception Thread renamed to "m_decodeThread"
1220316.492:017c:warn:threadname:NtSetInformationThread Thread renamed to L"wine_xinput_hid_update"
1220316.499:0180:warn:threadname:NtSetInformationThread Thread renamed to L"wine_sechost_device_notify"
1220316.506:0184:warn:threadname:NtSetInformationThread Thread renamed to L"wine_rpcrt4_io"
1220316.514:0188:warn:threadname:NtSetInformationThread Thread renamed to L"wine_threadpool_worker"
1220316.666:0024:warn:threadname:dispatch_exception Thread ID 018c renamed to "Render"
1220316.667:018c:warn:threadname:dispatch_exception Thread renamed to "Render"
1220316.887:0024:warn:threadname:dispatch_exception Thread ID 0190 renamed to "TEXDECODE"
1220316.890:0190:warn:threadname:dispatch_exception Thread renamed to "TEXDECODE"
1220316.894:0024:warn:threadname:dispatch_exception Thread ID 0194 renamed to "NuUbershaderBytecodeCache::WorkerThreadFunc"
1220316.894:0024:warn:threadname:dispatch_exception Thread ID 0198 renamed to "NuUbershaderBytecodeCache::WorkerThreadFunc"
1220316.895:0024:warn:threadname:dispatch_exception Thread ID 019c renamed to "NuUbershaderBytecodeCache::WorkerThreadFunc"
1220316.897:0194:warn:threadname:dispatch_exception Thread renamed to "NuUbershaderBytecodeCache::Work"
1220316.897:0024:warn:threadname:dispatch_exception Thread ID 01a0 renamed to "NuUbershaderBytecodeCache::WorkerThreadFunc"
1220316.899:0024:warn:threadname:dispatch_exception Thread ID 01a4 renamed to "NuShaderManagerGen::WorkerThreadFunc"
1220316.900:0198:warn:threadname:dispatch_exception Thread renamed to "NuUbershaderBytecodeCache::Work"
1220316.902:019c:warn:threadname:dispatch_exception Thread renamed to "NuUbershaderBytecodeCache::Work"
1220316.903:01a0:warn:threadname:dispatch_exception Thread renamed to "NuUbershaderBytecodeCache::Work"
1220316.904:01a4:warn:threadname:dispatch_exception Thread renamed to "NuShaderManagerGen::WorkerThrea"

I’ve created block list as an array of thread names and decided to force skipping thread renamings from block list. After that I’ve recompiled nt.dll:

make dlls/ntdll/all
sudo make install

Nothing changed.

Hypothesis: avoid locking at all

Then I re-used the same block list again but decided to prevent locking on critical section in the first place.

This time the game entered endless loop. As a side effect, less started encountering segmentation faults.

Log analysis

The whole experience of digging through huge logs and jumping between grep and less was really awful. When less started failing, I became angry. Of course, I have picked the tool that I am using on a daily basis: Haskell.

{- build-depends:     base ^>=4.17.2.1
                    , bytestring
                    , conduit
                    , directory
                    , filepath
                    , optparse-applicative
-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Main where

import Conduit
import Control.Monad (join, when)
import Data.ByteString (ByteString)
import Data.Maybe (isJust)
import Options.Applicative
import System.Directory
import System.FilePath
import Text.Read (readMaybe)

import qualified Data.ByteString.Char8 as BSC
import qualified Data.Conduit.Combinators as CC

main :: IO ()
main = join $ execParser (info opts idm)

-- * CLI

opts :: Parser (IO ())
opts = subparser
  ( command "clean" (info clean (progDesc "Clean log output, if present"))
 <> command "run" (info start (progDesc "Run log analyzer"))
  )

inputArg :: Parser FilePath
inputArg = strOption (metavar "FILE" <> long "input" <> short 'i')

outArg :: Parser FilePath
outArg = strOption (metavar "DIR" <> long "output" <> short 'o')

clean :: Parser (IO ())
clean = runClean <$> outArg

start :: Parser (IO ())
start = run <$> inputArg <*> outArg

-- * File system helpers

withDirectory :: FilePath -> IO () -> IO ()
withDirectory dir continue = do
  doesDirectoryExist dir >>= \case
    False -> createDirectoryIfMissing True dir >> continue
    True -> continue

withFile :: FilePath -> IO () -> IO ()
withFile file continue =  do
  doesFileExist file >>= \case
    False -> error $ "File does not exist: " <> file
    True -> continue

-- * Logic

runClean :: FilePath -> IO ()
runClean out = withDirectory out $ removeDirectoryRecursive out

run :: FilePath -> FilePath -> IO ()
run inputFile outputDir = withDirectory outputDir $ withFile inputFile $ do
  runResourceT $ runConduit $
    sourceFile inputFile
    .| CC.linesUnboundedAscii
    .| CC.mapM (liftIO . lineBS outputDir)
    .| CC.sinkNull

lineBS :: FilePath -> ByteString -> IO ()
lineBS out l = case BSC.split ':' l of
  [] -> pure ()
  _ : [] -> pure ()
  t : tid : rest -> processLineMaybe out t tid rest

processLineMaybe :: FilePath -> ByteString -> ByteString -> [ByteString] -> IO ()
processLineMaybe out bt btid = \case
  [] -> pure ()
  bs@(b:_)
    | not (isTimestamp bt && isThreadId btid) -> pure ()
    | "Call " `BSC.isPrefixOf` b || "Ret " `BSC.isPrefixOf` b ->
        appendThreadLog out bt btid bs
    | otherwise -> case bs of
        _ : [] -> pure ()
        _ : _ : [] -> pure ()
        lvl : logsrc : _rest -> do
          appendThreadLog out bt btid bs
          appendLogSource out bt btid logsrc bs
          when (lvl `elem` ["warn", "err"]) $ appendLogLevel out bt btid lvl bs

appendThreadLog :: FilePath -> ByteString -> ByteString -> [ByteString] -> IO ()
appendThreadLog out bt btid bs = do
  let file = out </> BSC.unpack btid <.> "log"
  BSC.appendFile file $ (BSC.intercalate ":" (bt : btid : bs)) <> "\n"

appendLogSource
  :: FilePath -> ByteString -> ByteString -> ByteString -> [ByteString] -> IO ()
appendLogSource out bt btid logsrc bs = do
  let file = out </> BSC.unpack logsrc <.> "log"
  BSC.appendFile file $ (BSC.intercalate ":" (bt : btid : bs)) <> "\n"
  
appendLogLevel
  :: FilePath -> ByteString -> ByteString -> ByteString -> [ByteString] -> IO ()
appendLogLevel out bt btid lvl bs = do
  let file = out </> BSC.unpack lvl <.> "log"
  BSC.appendFile file $ (BSC.intercalate ":" (bt : btid : bs)) <> "\n"
  
-- * Parsers

isTimestamp :: ByteString -> Bool
isTimestamp = isJust . readMaybe @Double . BSC.unpack

isThreadId :: ByteString -> Bool
isThreadId = isJust . readMaybe @Int . BSC.unpack . ("0x" <>)

The whole log was split onto many small log files: - by threads, - by channels, - and by log levels.

That was a game changer (pun intended).

CWD

I have started to read all log files separately, thread by thread, channel by channel. And stumbled upon interesting finding. Game was trying to read STUFF all the time!

Some threads tried to access files from stuff directory.

1220316.237:0150:trace:file:CreateFileW L"\\?\\stuff\\ttbmetadata.xml" GENERIC_READ FILE_SHARE_READ  creation 3 attributes 0x0
1220316.237:0190:trace:file:CreateFileW L"\\?\\stuff\\LENSFLARECORONA_DX11.TEX" GENERIC_READ FILE_SHARE_READ  creation 3 attributes 0x0

It could not find files but it tried to access root (/) and home ($HOME) directories of host system and my user. Those files were something new to me. It is not following naming convention of directories on file system. It was a game itself!

All that time I was launching the game from $HOME. I have modified runner script to navigate into game directory and tried again.

export WINEDEBUG="+timestamp,+dxgi,+d3d,+loadall,+module,+winediag,+warn,+debug,+vulkan,+thread,+sync,+msg,+key,+threadname,+file,+debugstr,+mmsys,+msync,+graphics,+message,+msvcrt,+setupapi,+rpc,+advapi,+imm,+gdi,+ole,+trace,+relay,trace+seh,+unwind,+virtual,+heap"
export WINEPREFIX="$HOME/lego7"

cd $WINEPREFIX/drive_c/Games/LMSH2
wine 'LEGOMARVEL2_DX11.exe' &> /dev/null # ~/LEGOMARVEL2_DX11.log

And I saw this:

Figure 6. game logos
Figure 6. game logos

It was such a relief to be able to see those logos and trademarks!

Debug loop

Use log produced by wine as main entry point of debug loop.

  1. For each impacted thread: examine thread log entries for clues.
  2. Analyse behaviour navigating by source files.
  3. Check for missing debug channels in source code of DLLs that raised a clue.
  4. If no clues left, examine THREAD_ATTACH calls to get the idea what libraries were involved.
  5. Add missing debug channels, re-run.
  6. Read until you find a clue. Repeat.

There is an excellent guide on how to debug application under Wine: Debugging Wine.

Timeline

From the explanation it could be seen that some talent needed to do any of that. Or that it was a matter of days to fix it. No, I am not that smart. In a nutshell it took me three months just to change the directory! Just think about it. Moreover, 6 wine prefices were corrupted beyond repair.

I have just had enough patience to go slowly through all the details. Here is the whole timeline:

  • Jan 29: BSOD, attempts to ressurect BootCamp partition
  • Feb 12: brew install wine-stable
  • Feb 13: switch from renderer=gl to renderer=vulkan
  • Feb 14: different versions of DXVK.
  • Feb 22: D3DMetal attempt.
  • Feb 29: managed to build DXVK from sources.
  • Mar 01: first black screen, Critical Section.
  • Mar 03: started to build MoltenVK from sources,
  • Mar 05: failed assertion Texture Descriptor Validation issue, native atomics regression.
  • Mar 06: re-test after MR being merged.
  • Mar 07: Downgrading MoltenVK to 1.2.7.
  • Mar 09: reproduced black screen the second time, Critical Section issue.
  • Mar 13: patching wine with ugly hack for the first time.
  • Mar 17: patching wine with even more ugly hack for the second time.
  • Apr 21: had idea about splitting logs.
  • Apr 28: split logs for the first time.
  • May 04: finished reading logs, found missing files.
  • May 05: changed entry point in the game, success!
Figure 7. game screenshot
Figure 7. game screenshot
Figure 8. another game screenshot
Figure 8. another game screenshot

Epilogue

In this brief blogpost I have briefly described:

  • the story on how to solve the issue with the game being restored after a sudden corruption of BootCamp partition,
  • Wine overview, tooling, installation, configuration and game app launch,
  • debug loop.

I hope you enjoyed reading it.

Thank you!

Buy Me a Coffee at ko-fi.com


Posted on 2024-05-12 by agr . Powered by Hakyll. Inspired by Yann Esposito.