Foreword
One should note that X is responsible for VT switching, meaning switching between an X session and console terminals. In other words, Ctrl+Alt+Fn is handled by X. If X is stopped, for example because it’s running under gdb, one can no longer switch to another VT. That’s why we’re recommending using a second machine to debug X. Nevertheless, here are some instructions to attempt debugging X with a single machine.
One-machine approach
This is a post-mortem approach. The idea is to run X with the -core option. Once it dies, a core file (/etc/X11/core) is generated, and can be loaded from gdb.
Follow these steps:
-
Getting a core file.
-
Loading a core file.
-
Displaying/saving a backtrace.
Two-machine approach
You pay the “need a second machine” price, but that buys you interactivity. Just log into the first machine from the second one, using ssh.
Follow these steps:
-
Attaching/Starting X from gdb.
-
Displaying/saving a backtrace.
Needed packages
Obviously, gdb is needed; xserver-xorg-core-dbg contains the debugging symbols for the server itself. Other needed packages can be determined by looking at the backtrace. FIXME: More info about that.
Actual gdb work
Getting a core file
-
Using gdm3: The idea is to tweak the daemon’s LocalXserverCommand setting, adding the -core option. As of gdm3 2.30, the defaults can be found in /usr/share/gdm/gdm.schemas. Sample /etc/gdm3/daemon.conf excerpt:
[daemon] LocalXserverCommand=/usr/bin/Xorg -br -verbose -audit 0 -novtswitch -core
-
Using kdm: One should look for the ServerArgsLocal variable in the /etc/kde4/kdm/kdmrc file, and add -core there. Example:
ServerArgsLocal=-br -nolisten tcp -core
-
Using xdm: It’s sufficient to add -core to the command configured through /etc/X11/xdm/Xservers, for example:
:0 local /usr/bin/X :0 vt7 -nolisten tcp -core
Loading a core file
That’s trivial, one just needs to pass both the core file and the path to the binary:
# gdb -c /etc/X11/core /usr/bin/Xorg
Now gdb is ready to display backtraces.
Attaching X from gdb
The way of starting X doesn’t really matter, as gdb makes it possible to attach a running process. If there’s a single X instance running, that will do the job:
# gdb attach $(pidof X) [---GDB starts---] (gdb) handle SIGPIPE nostop (gdb) cont
If there are several instances, one can use ps aux to determine the PID of the appropriate instance (2nd column → $pid), and then attach it:
# gdb attach $pid [---GDB starts---] (gdb) handle SIGPIPE nostop (gdb) cont
Starting X from gdb
In case X crashes at start-up, one can start X from gdb in the following way. In this example, the only parameter is the display, but more parameters can be added.
# gdb --args Xorg :0 [---GDB starts---] (gdb) handle SIGPIPE nostop (gdb) run
What is SIGPIPE?
SIGPIPE is a signal that can reach the X server quite easily, especially when performing a VT switch, or refreshing large parts of the screen. That’s why we ask gdb not to stop when such a signal is caught, thanks to the handle SIGPIPE nostop command.
How to display a backtrace?
Once X is crashed, for example because it received a SIGSEGV (segmentation fault, usually because of a NULL pointer dereference), or a SIGBUS, one gets back to the gdb prompt. One can then request a backtrace (bt) or a full backtrace (bt full). The latter is what developers are usually interested in, because variable values are also available.
(gdb) bt (gdb) bt full
How to save a backtrace?
To save a recording of the gdb session to a file (gdb.txt by default):
(gdb) set logging on
To save in a different file, use this instead:
(gdb) set logging file my-file.txt (gdb) set logging on
Once logging is enabled, you can request a (full) backtrace using the previous commands.