Debugging
This article explains how to debug Arx Fatalis on the different targeted platforms.
Linux
TODO: This information is outdated as we have a native linux build now.
Currently arx is not compiled as a native linux application, but as a winelib application that must be run with wine. This makes debugging a little more tricky.
Gdb
Using gdb allows to interact with C++ symbols. Since arx.exe.so is a winelib application, you can't run it directly with gdb , but winedbg supports automatically attaching gdb to the process when it starts:
WINEDEBUG=-all winedbg --gdb arx.exe.so
However, wine causes many segmentation faults internally (and handles them), which causes gdb to suspend the execution of the program. To tell gdb not to do this, enter the following into the gdb prompt:
handle SIGSEGV nostop noprint
This also prevents gdb from catching any segmentation faults caused by arx, which then exits and gdb just prints "Program exited normally." In those cases plain winedbg may still be needed.
Wine will also send a SIGTRAP once on startup, but SIGTRAP is also used by gdb, so ignoring it disables all breakpoints.
Many stl classes check for error conditions when compiling in debug mode and throw an exception on failure. To get a backtrace for where the exception was thrown, add a breakpoint on the exception constructor. Enter this in the gdb prompt.
b std::logic_error::logic_error
b std::runtime_error::runtime_error
If entering these commands into gdb on each run is to cumbersome, you can create a file called ".gdbinit" that is loaded by gdb on startup and contains default settings for gdb:
set breakpoint pending on
b std::logic_error::logic_error
b std::runtime_error::runtime_error
handle SIGSEGV nostop noprint
The extra "set breakpoint pending on" is needed because .gdbinit is loaded before any libraries and therefore gdb will not be able to find the breakpoints right away.
With this configuration, gdb will still stop twice during startup, once for a SIGSEGV and once for a SIGTRAP, both in wine. Use cont
or just c
each time to continue.
Attaching to existing processes
If arx has been started normally with wine you can also attach gdb to the running process, however each gdb session can only attach to one thread.
Start gdb using gdb wine
and then find the tids of the arx threads using ps -A -L | grep arx.exe.so
. The ids needed are those from the second column and the first result (same id for both columns) should be the main thread. Attach to it in the gdb promt using attach <tid>
.
Valgrind
Callgrind
Callgrind can be used to profile arx and generate a detailed call graph which can then be viewed using tools like KCachegrind. Like other valgrind tools, callgrind will considerably slow down the execution of the profiled program. Callgrind can be started using:
valgrind --tool=callgrind -- ./arx
Memcheck
Memcheck can detect memory access errors such as accessing unallocated/freed data, buffer overruns, using uninitialized values and memory leaks. To generate a very detailed report, run arx under valgrind with this command:
valgrind --tool=memcheck --error-limit=no --leak-check=full "--log-file=arx-valgrind-%p.log" -v --track-origins=yes -- ./arx
Memcheck will slow down the program even more than callgrind. Because the timing code in arx isn't very good, this can cause some cutscenes to fail (for example, the intro never finishes).
To make memcheck run faster, you can disable some of it's features that you don't need:
- Memory leaks: --leak-check=no
- Uninitialized values: --track-origins=no --undef-value-errors=no
For a full list of options see the memcheck manual
Because of bugs in libraries or because valgrind doesn't understand how some OpenGL implementations map HW buffers (notably the AMD binary drivers), there will most likely be a lot of false positives. Valgrind supports suppression files to handle bugs libraries that you don't want to debug: --suppressions=suppressions.txt
. This is not a very good solution for the HW buffers that valgrind doesn't understand as they are also accessed directly from arx code.
A starting point for a suppression file is:
# Valgrind doesn't understand how the ATI binrary drivers map HW buffers. # Some of these might be real bugs in the drivers! { fglrx_cond Memcheck:Cond ... obj:*fglrx_dri.so* } { fglrx_value4 Memcheck:Value4 ... obj:*fglrx_dri.so* } { fglrx_addr4 Memcheck:Addr4 ... obj:*fglrx_dri.so* } { fglrx_value1 Memcheck:Value1 ... obj:*fglrx_dri.so* } { fglrx_addr1 Memcheck:Addr1 ... obj:*fglrx_dri.so* } { fglrx_value2 Memcheck:Value2 ... obj:*fglrx_dri.so* } { fglrx_addr2 Memcheck:Addr2 ... obj:*fglrx_dri.so* } { fglrx_value8 Memcheck:Value8 ... obj:*fglrx_dri.so* } { fglrx_addr8 Memcheck:Addr8 ... obj:*fglrx_dri.so* } { fglrx_overlap Memcheck:Overlap ... obj:*fglrx_dri.so* } { fglrx_param Memcheck:Param ... obj:*fglrx_dri.so* } # Alsa is buggy, from http://winezeug.googlecode.com/svn/trunk/valgrind/valgrind-suppressions { suppress_libasound_overlap Memcheck:Overlap fun:memcpy obj:/usr/lib*/libasound.so.2.0.0 } { suppress_libasound_connect Memcheck:Param socketcall.connect(serv_addr..sun_path) obj:* obj:/usr/lib*/libasound.so.2.0.0 } { suppress_libasound_connect2 Memcheck:Cond fun:snd_pcm_direct_client_connect obj:/usr/lib*/libasound.so.2.0.0 } { suppress_libasound_bind Memcheck:Param socketcall.bind(my_addr..sun_path) obj:* obj:/usr/lib*/libasound.so.2.0.0 } { suppress_libasound_ioctl Memcheck:Param ioctl(arg) obj:* obj:/usr/lib*/libasound.so.2.0.0 } { suppress_libasound_ioctl2 Memcheck:Param ioctl(arg) obj:* fun:ioctl obj:/usr/lib*/libasound.so.2.0.0 } { suppress_libasound_semctl Memcheck:Param semctl(IPC_SET, arg.buf) obj:* obj:/usr/lib*/libasound.so.2.0.0 }