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
gdm33.4.1 and above: uncommentEnable = truein the[debug]section of the/etc/gdm3/daemon.conffile. - 
Using an older
gdm3package: The idea is to tweak the daemon’sLocalXserverCommandsetting, adding the-coreoption. As ofgdm3 2.30, the defaults can be found in/usr/share/gdm/gdm.schemas. Sample/etc/gdm3/daemon.confexcerpt: 
[daemon]
LocalXserverCommand=/usr/bin/Xorg -br -verbose -audit 0 -novtswitch -core
- 
Using
kdm: One should look for theServerArgsLocalvariable in the/etc/kde4/kdm/kdmrcfile, and add-corethere. Example: 
ServerArgsLocal=-br -nolisten tcp -core
- 
Using
xdm: It’s sufficient to add-coreto 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.