Proton/docs/DEBUGGING-LINUX.md
2025-01-27 10:21:08 +02:00

8.0 KiB

Proton Debugging Tips For Linux / Wine Developers

Table Of Contents

Debug Proton Builds

Debug builds contain symbols and should be used for live debugging session.

  1. Locate the version of proton you are using in your Steam Library (e.g. Proton Experimental), click on a cog and select properties.

  1. In the "betas" tab select "debug - unstripped".

  1. Chosen Proton version will now download and update and the symbols will be available.

For instruction on how to make a custom debug build see README.md#debug-builds.

Attaching A Debugger

Running GDB and attaching to a running .exe works out of the box:

$ ps | grep Game
2263566 ?        tsl    0:09 Z:\home\ivyl\.local\share\Steam\steamapps\common\Game\Game.exe

$ gdb
GNU gdb (GDB) 14.1
...
(gdb) attach 2263566
Attaching to process 2263566
[New LWP 2263569]
...
0x000075dce0cd788d in ?? ()

With Proton Experimental (and >= 9.0-2), GDB should be able to load the symbols right away. However, for the most seamless experience you will want to use a custom version of GDB with a couple of patches to better integrate with Wine.

You can find this fork at https://gitlab.winehq.org/rbernon/binutils-gdb, which you can build with configure && make all-gdb && make install-gdb. Make sure to have python development packages installed, as GDB python support will be required.

NOTE: using a debug Proton build will greatly improve the experience.

With this custom GDB you can source Wine's custom unwinder in your ~/.gdbinit, and you will get backtraces across PE / unix boundaries.

With older Proton you can use source wine's gdbinit to make load-symbol-files command available that uses /proc/$pid/maps to load the symbols:

(gdb) source ~/src/proton/wine/tools/gdbinit.py

(gdb) load-symbol-files
loading symbols for /home/ivyl/.local/share/Steam/steamapps/common/Proton - Experimental/files/lib64/wine/x86_64-windows/kernelbase.dll
...

(gdb) bt
#0  0x000075dce0cd788d in ?? ()
#1  0x000075dcdf26e842 in futex_wait (timeout=0x0, val=0, addr=0x75dcdd8383e0) at ../src-wine/dlls/ntdll/unix/sync.c:127
#2  NtWaitForAlertByThreadId (address=<optimized out>, timeout=0x0) at ../src-wine/dlls/ntdll/unix/sync.c:2658
#3  <signal handler called>
#4  0x000000017000ebb4 in NtWaitForAlertByThreadId ()
#5  0x00000001700367f9 in RtlWaitOnAddress (addr=addr@entry=0x4850970, cmp=0x3d12fa9c, cmp@entry=0x3d12fabc, size=size@entry=4, timeout=timeout@entry=0x0) at ../src-wine/dlls/ntdll/sync.c:941
#6  0x000000017003276a in RtlSleepConditionVariableSRW (variable=0x4850970, lock=0x4850908, timeout=0x0, flags=<optimized out>) at ../src-wine/dlls/ntdll/sync.c:837
#7  0x000000007b061e41 in SleepConditionVariableSRW (variable=<optimized out>, lock=<optimized out>, timeout=<optimized out>, flags=<optimized out>) at ../src-wine/dlls/kernelbase/sync.c:1064
#8  0x000000035915c892 in dxvk::condition_variable::wait (lock=..., this=0x4850970) at ../src-dxvk/src/dxvk/../util/log/../thread.h:251
#9  dxvk::condition_variable::wait<dxvk::DxvkPipelineWorkers::runWorker(dxvk::DxvkPipelinePriority)::<lambda()> > (pred=..., lock=..., this=0x4850970) at ../src-dxvk/src/dxvk/../util/log/../thread.h:257
#10 dxvk::DxvkPipelineWorkers::runWorker (this=0x48508f0, maxPriority=dxvk::DxvkPipelinePriority::Normal) at ../src-dxvk/src/dxvk/dxvk_pipemanager.cpp:140
#11 0x00000003591a7781 in std::function<void ()>::operator()() const (this=0x4852ee0) at /usr/x86_64-w64-mingw32/include/c++/10.3.0/bits/std_function.h:622
#12 dxvk::thread::threadProc (arg=0x4852ed0, arg@entry=<error reading variable: value has been optimized out>) at ../src-dxvk/src/util/thread.cpp:68
#13 0x000000007b6146ed in BaseThreadInitThunk (unknown=<optimized out>, entry=<optimized out>, arg=<optimized out>) at ../src-wine/dlls/kernel32/thread.c:61
#14 0x000000017000f1a7 in RtlUserThreadStart ()
#15 0x0000000000000000 in ?? ()

Attaching Before The Program Starts

Launch the software with PROTON_WAIT_ATTACH=1 %command% set as the launch command in game's properties. Our steam.exe shim will then wait for debugger and only then exec the proper executable.

Make sure that you follow child processes:

set follow-fork-mode child

Getting Shell Inside Of The Steam Runtime

Set your launch options to: PROTON_LOG=1 STEAM_COMPAT_LAUNCHER_SERVICE=proton %command%

Then in steam-$GAMEID.log you should see the following:

Starting program with command-launcher service.

To run commands in the per-app container, use a command like:

/home/ivyl/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/pressure-vessel/bin/steam-runtime-launch-client \
	--bus-name=:1.307 \
	--directory='' \
	-- \
	bash

After invoking it you end up in a shell with the environment variables, including WINEPREFIX are set and you can invoke wine directly, e.g.:

[ivyl@crabcraft x64]$ wine winedbg
Wine-dbg> info process
 pid      threads  executable (all id:s are in hex)
 0000028c 1        'start.exe'
 0000029c 1        \_ 'winedbg.exe'
=000002a4 1           \_ 'winedbg.exe'
 00000294 2        \_ 'conhost.exe'
 00000030 10       'services.exe'
 000000e4 6        \_ 'rpcss.exe'
 000000b0 3        \_ 'svchost.exe'
 00000094 6        \_ 'plugplay.exe'
 00000064 9        \_ 'winedevice.exe'
 0000003c 8        \_ 'winedevice.exe'
 00000020 3        'steam.exe'
 00000128 62       \_ 'Game.exe'
 000000d0 3        \_ 'explorer.exe'
 0000010c 3           \_ 'tabtip.exe'

NOTE: If you need a predictable bus name instead of the unique connection name (the :1.307 from example above) you can use com.steampowered.App1234567 where 1234567 is the Steam App ID for the title you are debugging.

You can always use a tool like qdbus to list available bus names.

Starting A Different Binary

If you want to start a different binary than the game's default you can use a few methods. All of the examples below will use winecfg.

Substitution

You can use the following launch option:

echo "%command%" | sed 's/proton waitforexitandrun .*/proton waitforexitandrun winecfg/' | sh

The full substitution of proton waitforexitandrun .* is necessary as the original %command% is very long and may contain multiple mentions of proton or the original binary.

Pressure-Vessel Shell

Pressure-vessel allows to spawn an xterm instead of launching Proton. This can be accomplished by setting PRESSURE_VESSEL_SHELL=instead. The easiest way is to set the launch option to:

PRESSURE_VESSEL_SHELL=instead %command%

The original coommand is then contained in $@, e.g.:

/home/ivyl/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/pressure-vessel/bin/steam-runtime-launcher-interface-0 container-runtime /home/ivyl/.local/share/Steam/steamapps/common/Proton - Experimental/proton waitforexitandrun /home/ivyl/.local/share/Steam/steamapps/common/Game/Game.exe

From this point you can invoke proton script as all the required environment variables are set. If you are copying the path from $@ make sure to escape or quote parts that contain spaces.

To start winecfg something like this can be entered:

"/home/ivyl/.local/share/Steam/steamapps/common/Proton - Experimental/proton" waitforexitandrun winecfg