Wenn ein Prozess unter Linux abstützt, kann der Kernel wichtige Informationen wie den Speicherinhalt sichern, damit man im Nachhinein das Problem untersuchen kann. Viele Probleme sind sehr komplex und lassen sich nur schwer reproduzieren, weshalb diese Coredumps hilfreich sind.

Bisher hatte ich die notwendigen Einstellungen immer von Hand vorgenommen, aber ich habe die Pakete systemd-coredump und corekeeper entdeckt. Das Paket systemd-coredump existiert noch nicht unter Debian 8, weshalb ich auf meinem Server corekeeper installiert habe. Beide scheinen auf den ersten Blick das selbe zu tun, nur ist corekeeper eine reine Debian-Entwicklung und systemd-coredump die Integration in systemd.

Prozesse ohne Coredumps finden

Die beiden Paketen kümmern sich um die geordnete Ablage der Coredumps und die Benachrichtigung des Admins über vorhandene Coredumps. Dass der Kernel einen Coredump bei einem Prozessabsturz tatsächlich schreibt, kann man für eine Shell (und deren Kinder) mit ulimit -c unlimited aktivieren und mit ulimit -c 0 deaktivieren.

Um zu sehen, für welche Prozesse keine Coredumps erzeugt werden, kann man in /proc nach diesen suchen, denn die Limits eines Prozesses werden unter /proc/PID/limits vom Kernel veröffentlicht.

# ps $(grep -l 'Max core file size        0' /proc/*/limits |cut -f3 -d/) |grep -v '\]$'
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:01 /sbin/init
  163 ?        Ss     0:00 /lib/systemd/systemd-journald
  174 ?        Ss     0:00 /lib/systemd/systemd-udevd
  334 ?        Ss     0:00 /lib/systemd/systemd-logind
  336 ?        Ss     0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
  342 ?        Ss     0:00 /lib/systemd/systemd-networkd
  343 ?        Ss     0:00 /usr/sbin/acpid
  346 ?        S<L    0:00 /usr/bin/atop -a -w /var/log/atop/atop_20170529 600
  356 tty1     Ss+    0:00 /sbin/agetty --noclear tty1 linux

  575 ?        S      0:00 avahi-daemon: chroot helper
 2606 ?        Ss     0:00 /usr/bin/ssh-agent /usr/bin/ck-launch-session /home/joerg/.xsession
 2619 pts/0    Ss     0:00 sudo HOME=/home/joerg zsh -69JNXZhims

Einige Prozesse wie ssh-agent und sudo scheinen Coredumps jedoch selbst wieder zu deaktivieren. Allerdings muss nach der Installation der Pakete auch erst einmal die Erzeugung der Coredumps aktiviert werden.

Coredumps für alle Prozesse bei systemd aktivieren

Damit man von den Diensten die entsprechenden Coredumps bekommt, muss man in /etc/systemd/system.conf die Einstellung

DefaultLimitCORE=infinity

setzen. Systemd startet dann alle Prozesse mit aktiviertem Coredump. Für Benutzerprozesse muss man in der Datei /etc/security/limits.conf die Einträge

# enable core dumps
* soft core unlimited
* hard core unlimited

ergänzen, so dass von PAM die Coredumps bei der Anmeldung aktiviert werden. Für weitere Einstellungen zur Verarbeitung von Coredumps sei nur auf /etc/systemd/coredump.conf verwiesen.

Coredumps mit coredumpctl auswerten

Die aufgezeichneten Sicherungen der Abstürze kann man sich leicht mit coredumpctl list auflisten oder mit coredumpctl info auswerten lassen oder mit coredumpctl debug den Debugger damit starten. Coredumpctl bietet auch noch verschiedene Filtermöglichkeiten, falls die Liste der Abstürze sehr groß wird.

Umfangreichere Ausgabe des Stacks

Für eine Fehlermeldung eines Programmabsturzes versuche ich immer so viele Informationen wie möglich mitzuteilen. Ab der Version 248 kennt coredumpctl die Option --debugger-args, womit es leicht ist, einen ausführlichen Stackverlauf auszugeben:

coredumpctl debug --debugger-arguments="-quiet -batch -ex 'bt full' -ex 'echo --- END ---\n\n'"

Mit älteren Versionen bedurfte es eines kleinen Hilfsprogramms gdb-bt-full, um gdb die notwendigen Optionen mitzugeben:

#!/bin/sh

exec gdb "$@" -quiet -batch -ex 'bt full' -ex 'echo --- END ---\n\n'

Dieses rufe ich dann mit coredumpctl --debugger ~/bin/gdb-bt-full debug auf und kann die Ausgabe komplett in den Fehlerbericht einfügen. Mehr Informationen kann man ohne zusätzliche Arbeit auch nicht liefern. Im Gegensatz zur Situation vor Jahren ist es jetzt mit coredumpctl sehr komfortabel und ich kann binnen von fünf Minuten einen Bugreport erstellen.

Debuginformationen unter Debian

Um die Sicherungen detailierter analysieren zu können, benötigt man die Debuginformationen zu den beteiligten Programmen und Bibliotheken. Debian stellt dafür zwei Möglichkeiten zur Auswahl: * die sehr einfache Variante über den Debuginfo-Server von Debian oder * die Pakete mit den Debuginformationen.

Debuginfo-Server einrichten

Ab Debian-Bullseye unterstützt gdb den Abruf der Debuginformationen von einem Server. Hierfür muss nur die Umgebungsvariable DEBUGINFOD_URLS gesetzt werden und gdb lädt beim Start die notwendigen Informationen vom Server: »Downloading separate debug info for …«.

Debian stellt hierfür den Server debuginfod.debian.net bereit. Um diesen zu nutzen ergänzt man entweder den Aufruf von coredumpctl zu

DEBUGINFOD_URLS="https://debuginfod.debian.net" coredumpctl debug --debugger-arguments=…

oder man setzt die Variable mit export DEBUGINFOD_URLS="https://debuginfod.debian.net" in dem Hilfsprogramms gdb-bt-full.

Die Dateien werden im Verzeichnis ~/.cache/debuginfod_client gespeichert, dessen Bereinigung man über die beiden Dateien cache_clean_interval_s und max_unused_age_s in dem Verzeichnis steuern kann.

Debuginfos aus dbgsym-Paketen

Wenn man keine Internetverbindung beim Debuggen verwenden will, kann man die Debuginformationen in den Paketen mit der Endung -dbgsym nutzen. Hierfür muss man in /etc/apt/sources.list einen entsprechenden Eintrag für seine Debian-Variante einfügen; zum Beispiel:

deb http://ftp.de.debian.org/debian-debug/ unstable-debug main non-free contrib

Coredumpctl zeichnet die Informationen leider nur beim Entstehen der Sicherung auf und ergänzt sie nicht nachdem die Informationen installiert wurden. Aber mit coredumpctl gdb kann man im gdb mit dem Befehl bt (backtrace) sich die Stelle des Absturzes ansehen:


(gdb) bt
#0  0x00007f0b5ffe4ce7 in __GI___select (nfds=1, readfds=0x7ffd83ca7e80, writefds=0x0, exceptfds=0x0, timeout=0x0) at ../sysdeps/unix/sysv/linux/select.c:41
#1  0x00007f0b5fcf3e14 in ?? () from /usr/lib/python2.7/lib-dynload/readline.x86_64-linux-gnu.so
#2  0x000055dd60d82496 in PyOS_Readline ()
#3  0x000055dd60d5ca96 in ?? ()
#4  0x000055dd60ded854 in PyParser_ASTFromFile ()
#5  0x000055dd60d8221d in PyRun_InteractiveOneFlags ()
#6  0x000055dd60d820d8 in PyRun_InteractiveLoopFlags ()
#7  0x000055dd60d67fe8 in ?? ()
#8  0x000055dd60dcd345 in Py_Main ()
#9  0x00007f0b5ff18f2a in __libc_start_main (main=0x55dd60dccc90 
, argc=1, argv=0x7ffd83ca83d8, init=, fini=, rtld_fini=, stack_end=0x7ffd83ca83c8) at ../csu/libc-start.c:310 #10 0x000055dd60dccbaa in _start ()

Anhand der … from … sieht man, welche Bibliotheken beteiligt waren, für die man mit dpkg -S … das Paket finden und dafür die Debuginformationen installieren kann. Nachdem die Informationen alle vorhanden sind, kann man gdb wieder aufrufen und erhält dann im Backtrace die genauen Angaben.

Mit apt source … kann man sich auch den Quelltext zu den Paketen holen, so dass man Probleme noch genauer analysieren kann. Manchmal zeigt sich, dass Abstürze durch Konfigurationseinstellungen bedingt sind, womit man vielleicht erst einmal um ein Problem herumarbeiten kann, bis es behoben ist.

Ich habe auch bemerkt, dass die Namen der betroffenen Dateien ebenso bei coredumpctl info erkennbar sind:


% coredumpctl info -1
           PID: 11333 (python)
           UID: 1000 (joerg)
           GID: 1000 (joerg)
        Signal: 3 (QUIT)
     Timestamp: Fri 2018-01-05 12:56:09 CET (9min ago)
  Command Line: python
    Executable: /usr/bin/python2.7
 Control Group: /user.slice/user-1000.slice/session-7.scope
          Unit: session-7.scope
         Slice: user-1000.slice
       Session: 7
     Owner UID: 1000 (joerg)
       Boot ID: ffb88c73dd1b40c29dd7f9280176f919
    Machine ID: 523cb54753234ed08c13ec497d0d3b64
      Hostname: zenbook
       Storage: /var/lib/systemd/coredump/core.python.1000.ffb88c73dd1b40c29dd7f9280176f919.11333.1515153369000000.lz4
       Message: Process 11333 (python) of user 1000 dumped core.

                Stack trace of thread 11333:
                #0  0x00007f0b5ffe4ce7 __GI___select (libc.so.6)
                #1  0x00007f0b5fcf3e14 n/a (readline.x86_64-linux-gnu.so)
                #2  0x000055dd60d82496 PyOS_Readline (python2.7)
                #3  0x000055dd60d5ca96 n/a (python2.7)
                #4  0x000055dd60ded854 PyParser_ASTFromFile (python2.7)
                #5  0x000055dd60d8221d PyRun_InteractiveOneFlags (python2.7)
                #6  0x000055dd60d820d8 PyRun_InteractiveLoopFlags (python2.7)
                #7  0x000055dd60d67fe8 n/a (python2.7)
                #8  0x000055dd60dcd345 Py_Main (python2.7)
                #9  0x00007f0b5ff18f2a __libc_start_main (libc.so.6)
                #10 0x000055dd60dccbaa _start (python2.7)

Coredumps für alle Prozesse bei corekeeper aktivieren

Für Benutzer werden von corekeeper bei der Anmeldung Coredumps aktiviert. Allerdings nicht für Dienste und andere Prozesse. Dies kann man aber leicht erreichen, indem man auf der initrd die Coredumps aktiviert, so dass der eigentliche Init-Prozess bereits mit aktivierten Coredumps gestartet wird und diese Einstellung auf alle Kinder vererbt.

Dies kann man mit folgendem Skript erreichen, das man als /etc/initramfs-tools/scripts/init-bottom/local speichert und diese Datei noch ausführbar setzt (chmod +x …). Damit die Initrd neu bauen (update-initramfs -u) und das System neu starten. Somit erhält man auch die abgestürzte PHP-Prozesse einen Coredump und kann eventuell mehr über die Ursache herausfinden oder ggf. den Coredump an einen Entwickler weiterleiten.


#!/bin/sh

case "$1" in
  # get pre-requisites
  prereqs)
    # Print the soft prerequisites here.  This is a space separated list of
    # names, of scripts that are in the same directory as this one, that
    # must be run before this one can be.
    exit 0
    ;;
esac

# Diese Datei wird von dem init-Prozess auf der initrd immer wieder ausgeführt
# und so kann für den init-Prozess selbst und alle Kinderprozesse der coredump
# aktiviert werden.
echo "ulimit -c unlimited" >> /conf/param.conf