Aus Linux-Magazin 05/2012

Kernel- und Treiberprogrammierung mit dem Linux-Kernel – Folge 62

© psdesign1, Fotolia

Für KGDB, den eingebauten Debugger des Linux-Kernels, gibt es die beiden Frontends KDB und GDB. Mit KDB und der richtigen Konfigurationsdatei kann es unter Ubuntu sofort losgehen. Wer Hochsprachen-Debugging mit GDB wünscht, installiert noch Virtualbox, Socat und einen speziellen Kernel.

Linux-Kernel-Debuggingist ein heikles Thema. Während Windows & Co. schon seit gefühlten Ewigkeiten einen Kerneldebugger zur Verfügung stellen, hat es bei Linux 16 Jahre gedauert, bis Torvalds bereit war einen “Kerneldebugger light” in die Quellen aufzunehmen. Seit 2008 ist der »kgdb« ein Bestandteil des Linux-Kerns, etwas später kam die Erweiterung »kdb« dazu.

Mitgeliefert: KDB

Dabei stellt »kdb« die einfachste Möglichkeit dar, den Kernel zu untersuchen. Viele Distributionen, beispielsweise auch Ubuntu, integrieren den KDB, allerdings ohne ihn gleich scharf zu schalten. Das ist jedoch schnell erledigt, denn sobald der Debugger die Schnittstellen kennt, über die er Kommandos bezieht und an die er die Ausgaben schicken soll, ist er auch schon aktiviert. Zur Auswahl stehen dafür die Konsole, repräsentiert durch das Keyboard und das Schlüsselwort »kbd« , sowie die serielle Schnittstelle (»ttyS0« ).

Die Schlüsselwörter schreibt der Superuser mit Hilfe von »echo« in die zum Kerneldebugger gehörende Konfigurationsdatei »kgdboc« . Die Variante für die Konsole (Keyboard) lässt sich unter Berücksichtigung der Option »kms« mit der folgenden Kommandozeile konfigurieren:

echo kms,kbd > /sys/module/kgdboc/parameters/kgdboc

Achtung: »kbd« steht hier für Keyboard und nicht für den KDB. Mit dieser Konfiguration friert das System ein, sobald eine Kernelkomponente abstürzt oder ein Kernelmodul die Funktion »panic()« aufruft. Root kann das System aber auch von Hand zum Debuggen einfrieren, indem er das Kommando

echo g >/proc/sysrq-trigger

eingibt oder die vier Tasten [Strg]+[Alt]+[S-Abf]+[G] gleichzeitig drückt. [G] steht dabei für das Kommando, um in den interaktiven Modus des Debuggers zu wechseln. Ein »help« im interaktiven Modus zeigt die weiteren Kommandos an. Der Anwender kann Speicherzellen auslesen und schreiben, die Prozessliste oder die letzten Kernelnachrichten anzeigen sowie Breakpoints setzen. Eines der wichtigsten Kommandos ist »go« , denn es veranlasst den Kernel dazu, seine Arbeit wieder aufzunehmen, als wäre gar nichts gewesen. Tabelle 1 listet die wichtigsten KDB-Kommandos auf.

<a href="#article_t1" class="table" srcset=

Tabelle 1: Wichtige Kommandos des Kerneldebuggers KDB” width=”300″ height=”233″ /> Tabelle 1: Wichtige Kommandos des Kerneldebuggers KDB

Nur Assembler-Code

So umfangreich die Möglichkeiten auch sind, zwei Probleme schränken den Einsatz des eingebauten Debugger-Frontends ein: Erstens unterstützt KDB kein Debugging auf Hochsprachen-Niveau und zeigt dem Anwender nur Assembler-Code. Zweitens funktioniert er nur einwandfrei, wenn er entweder für die serielle Schnittstelle »ttyS0« konfiguriert ist oder von einer normalen Konsole aus aufgerufen wird.

Immerhin ist es dank Kernel Mode Switching (KMS) mittlerweile möglich, den Debugger auch aus dem grafischen Modus mit X.org zu verwenden. In diesem Fall schaltet der Debugger bei der Aktivierung auf die erste Konsole, um von hier aus das System zu untersuchen. Sobald der Anwender ihn per »go« beendet, schaltet der Debugger wieder zurück zur grafische Oberfläche. Allerdings funktioniert das nicht in jedem Fall, es gibt beispielsweise Probleme in einer mittels Virtualbox virtualisierten Umgebung. Außerdem muss der Superuser KMS explizit durch Angabe des Schlüsselworts »kms« aktivieren.

Die klassische Variante des Kerneldebuggings nutzt aber ohnehin die serielle Schnittstelle zur Ein- und Ausgabe. Dumm nur, dass man zwei Rechner dafür benötigt. Außerdem besitzen moderne Desktop-PCs, Note- oder Netbooks gar keine serielle Schnittstelle mehr.

Doch genau hier kann eine Virtualisierungslösung aushelfen. Schnell lässt sich mit Virtualbox ein Rechner zusammenklicken und mit einer seriellen Schnittstelle versehen. Diese ist als so genannte Hostpipe zu konfigurieren (Abbildung 1). In Virtualbox kann ein Linux, beispielsweise Ubuntu 11.10, als so genanntes Debug-Target laufen. Den Kerneldebugger des Targets schaltet Root mit folgendem Kommando scharf:

Abbildung 1: Debuggen eines virtualisierten Linux-Systems auf dem lokalen Rechner: Die serielle Schnittstelle konfiguriert der Virtualbox-Anwender als Hostpipe.

Abbildung 1: Debuggen eines virtualisierten Linux-Systems auf dem lokalen Rechner: Die serielle Schnittstelle konfiguriert der Virtualbox-Anwender als Hostpipe.

echo ttyS0,115200 > /sys/module/kgdboc/parameters/kgdboc

Zur Kommunikation mit dem Debugger des Targets muss auf dem Hostsystem ein Terminalprogramm installiert sein, hier bietet sich »minicom« an. Leider kann Minicom nicht direkt mit der Hostpipe (im Beispiel »/tmp/com1« ) kommunizieren. Daher ist weitere Software erforderlich, die zwischen Hostpipe und Minicom vermittelt. Das übernimmt »socat« , sodass das gesamte Setup neben Virtualbox mit einem Linux aus Socat und Minicom besteht (siehe Abbildung 2).

Abbildung 2: Debuggen des Kernels in einer virtualisierten Umgebung.

Abbildung 2: Debuggen des Kernels in einer virtualisierten Umgebung.

Komplexes Setup

Nach dem Start unter Angabe der Hostpipe gibt »socat« den Namen der Gerätedatei aus, über die das Terminalprogramm auf die Daten zugreifen kann. Im Fall von Abbildung 3 beispielsweise ist das »/dev/pts/7« . Die Nummer dieses Pseudoterminals variiert von Mal zu Mal. Ist der Name der Gerätedatei gefunden, startet Root das Terminalprogramm:

Abbildung 3: Socat vermittelt zwischen Virtualbox und dem Debugger-Frontend.

Abbildung 3: Socat vermittelt zwischen Virtualbox und dem Debugger-Frontend.

minicom -D /dev/pts/7

Aktiviert er nun den Debugger – beispielsweise mit der magischen Tastenkombination [Strg]+[Alt]+[S-Abf]+[G] –, reicht Virtualbox sämtliche Ausgaben der seriellen Schnittstelle an die konfigurierte Hostpipe weiter und diese schließlich per »socat« an das Terminalprogramm. Minicom zeigt den Debugger-Prompt, nimmt Kommandos entgegen und sendet sie an KDB. Dessen Hilfsmenü lässt sich übrigens aufrufen, indem man gleichzeitig die Tasten [Strg]+[A] und anschließend [Z] drückt. Um Minicom zu verlassen, sind [Strg]+[A] und danach [Q] und [Return] einzugeben.

Hohes Niveau

Wie bereits erwähnt, ist KDB nicht in der Lage, auf Hochsprachen-Niveau zu debuggen. Ein auf dem Hostsystem laufender GDB dagegen ermöglicht es, Breakpoints auf einzelne Zeilen des C-Quelltextes zu setzen.

Der GNU-Debugger spricht unter Umgehung des KDB mit dem eigentlichen Debug-Core des Kernels, dem KGDB. Diese Basiskomponente des Kerneldebuggers bezeichnet man auch als Debugserver. KGDB kann Speicherzellen lesen und schreiben, Breakpoints setzen und Codesequenzen reaktivieren. Darüber hinaus bedient er die serielle Schnittstelle, um darüber Kommandos zu empfangen und Debuginformationen auszugeben.

Für seine Ein- und Ausgaben kann KGDB nicht auf die normalen Linux-Treiber zurückgreifen. Die meisten Treiber arbeiten nämlich über Interrupts, die während des Debuggens nicht benutzbar sind. Der Debugserver überprüft stattdessen per Polling regelmäßig und in kurzen Abständen, ob neue Daten für ihn vorliegen (siehe Abbildung 4). Der KGDB stellt nur das Backend dar.

Abbildung 4: Das Debug-Subsystem des Kernels: Ein Frontend wie GDB auf dem Debughost (links) kommuniziert per »kgdboc«-Protokoll mit dem Debugserver KGDB auf dem Target (rechts).

Abbildung 4: Das Debug-Subsystem des Kernels: Ein Frontend wie GDB auf dem Debughost (links) kommuniziert per »kgdboc«-Protokoll mit dem Debugserver KGDB auf dem Target (rechts).

Für komplexere Aufgaben wie etwa das Zuordnen von Maschinencode zu Quellcodezeilen ist ein Frontend erforderlich, das mit GDB zur Verfügung steht. Der GNU-Debugger wühlt sich durch den Quellcode und führt die Umsetzung zwischen Symbolen und realen Speicheradressen durch. Da der GDB funktionierendes Speicher- und Dateimanagement benötigt, läuft er auf einem zweiten Rechner, dem Debughost. Das Protokoll, über das Debugserver und Debughost kommunizieren, nennt sich »kgdboc« (“kdgb over console”).

Für das Hochsprachen-Debugging sind allerdings einige Vorarbeiten notwendig. Der GDB benötigt nämlich nicht nur den Quellcode des Kernels, sondern auch das unkomprimierte Kernelobjekt selbst, und zwar inklusive der Symbolinformationen. Zudem muss der Kernel die notwendigen Debugkomponenten integriert haben. Das ist in vielen Fällen mit dem Neukompilieren auf dem Target gleichzusetzen (siehe Kasten “Debug-Kernel für Ubuntu 11.10”). Außerdem ist darauf zu achten, dass Debughost und Debugtarget zueinander passen: Das Debuggen eines 32-Bit-Targets mit einem 64-Bit-Host führte in den Versuchen der Autoren zu Problemen.

Debug-Kernel für Ubuntu 11.10

KGDB und KDB sind Teile des Linux-Quellcodes, das Einspielen separater Patches entfällt. Allerdings sind die Debugger bei der Kernelkonfiguration zu berücksichtigen. Auch wenn Distributionen wie Ubuntu einen Kernel mit Debugger ausliefern, ist die Unterstützung unvollständig: Der Debugger hat nur lesenden Zugriff auf Code und Daten und kann daher keine Breakpoints setzen, zudem fehlen die Symbolinformationen. Das macht es erforderlich, einen eigenen Debugkernel zu bauen.

Zunächst müssen mit »build-essential« , »libncurses-dev« und »linux-source« die notwendigen Pakete installiert sein (siehe Listing 1). Nach der Installation wechselt Root in das Verzeichnis »/usr/src« und packt dort den Linux-Quellcode aus, den Ubuntus Paketmanager als komprimiertes Archiv abgelegt hat. Danach wechselt er in das ausgepackte Quellcodeverzeichnis und kopiert die aktuelle Kernelkonfiguration in den Ordner.

Da das Kompilieren eines vollständigen Ubuntu-Kernels mit all seinen Modulen sehr viel Zeit verschlingt, empfiehlt sich eine Alternative: Das Make-Target »localmodconfig« konfiguriert den Linux-Kernel mit genau jenen Treibern, die aktuell im System aktiv sind. Falls »make localmodconfig« Fragen stellt, reicht das Drücken der Return-Taste für die Standardeinstellung. Danach ist aber noch »make menuconfig« an der Reihe, um einige Optionen zu aktivieren beziehungsweise zu deaktivieren.

Die relevanten Einstellungen finden sich sämtlich unterhalb des Menüpunkts »Kernel Hacking« . Benötigt werden die Optionen »Magic SysRq key« , »Kernel debugging« , »Compile the kernel with debug info« . Außerdem muss der Anwender die Option »KGDB: kernel debugger« aktivieren und darauf achten, dass im zugehörigen Untermenü die Punkte »KGDB_KDB: include kdb frontend for kgdb« und »KGDB_KDB: keyboard as input device« ausgewählt sind.

Zu deaktivieren sind dagegen die Optionen »Write protect kernel read-only data structures« und »Set loadable kernel module data as NX and test as RO« . Ist der Kernel konfiguriert, wird er zusammen mit den Modulen generiert. Der Parameter »-j« legt übrigens fest, auf wie viele Prozessoren »make« beim Übersetzen zurückgreifen kann. Das Generieren des Kernels dauert je nach Leistung der Maschine wenige Minuten, aber manchmal auch eine Stunde. Noch mehr Geduld ist notwendig, wenn es in der virtuellen Maschine stattfindet.

Nach dem Kompilieren geht es an die Installation. Zunächst sind die Module per »make modules-install« auf dem Target an die richtige Stelle zu kopieren. Ein Blick in das Verzeichnis »/lib/modules« mit Hilfe des Kommandos »ls -lrt« verrät in seinem letzten Eintrag, welche Kernelversion genau entstanden ist, beispielsweise 3.0.13. Mit dieser Information kopiert der Anwender die Kernelkonfiguration und den Kernel selbst in das Verzeichnis »/boot« und erzeugt die zugehörige initiale Ramdisk. Im Verzeichnis »/boot« schließlich findet das Skript »update-grub« den neuen Kernel und konfiguriert den Bootloader. Mit dem nächsten Booten ist der neue Systemkern aktiv.

Listing 1

Debug-Kernel erzeugen und installieren

01 # Notwendige Pakete installieren 02 apt-get install build-essential libncurses-dev linux-source 03 cd /usr/src/ 04 tar xvfj linux-source-3.0.0.tar.bz2 05 # Kernel konfigurieren 06 cd linux-source-3.0.0/ 07 cp /boot/config-3.0.0-15-generic . 08 make localmodconfig 09 # Debug-Optionen einstellen 10 make menuconfig 11 # Kernel generieren: 12 make -j4 bzImage modules 13 make modules-install 14 # Kernelversion herausfinden 15 ls -lrt /lib/modules 16 # Installation 17 cp .config /boot/config.3.0.13 18 cp boot/arch/x86/boot/bzImage /boot/vmlinux-3.0.13 19 update-initramfs -c -k 3.0.13 20 update-grub

Symbolinformationen

Nach dem Neu-Übersetzen auf dem Target hat das Kernel-Buildsystem zwei Kernelobjekte angelegt. Auf einem x86-Rechner findet sich im Verzeichnis »arch/x86/boot/« das komprimierte Objekt »bz–Image« . Das unkomprimierte, mit Symbolinformationen versehene und für GDB benötigte Kernelobjekt liegt aber unter dem Namen »vmlinux« im Startverzeichnis des Quellcodes. Dieses Kernelobjekt kopiert der Anwender zusammen mit dem Quellcode auf die Hostmaschine. Am besten überträgt er den kompletten Dateibaum mit dem folgenden Kommando vom Target zum Host:

scp -r /usr/src/linux-3.0.0/ Debughost:/usr/src/

Ist die Target-Maschine mit dem neuen Kernel gebootet, kann die Debugsession beginnen. Dazu startet man den »gdb« mit dem Namen des Kernelobjekts als Parameter und verbindet ihn mit Hilfe des Kommandos »target remote /dev/pts/7« mit dem Debugserver (siehe Abbildung 5). Selbstverständlich ist der Schnittstellenname (im Beispiel »/dev/pts/7« ) an den von Socat ausgegebenen Wert anzupassen.

Abbildung 5: Der GDB ermöglicht nach Kopplung mit dem KGDB das Debugging auf Hochsprachen-Niveau.

Abbildung 5: Der GDB ermöglicht nach Kopplung mit dem KGDB das Debugging auf Hochsprachen-Niveau.

Kommunikationsprobleme

Der GDB wartet jetzt darauf, dass auf dem Target der Debugger startet. Sobald das geschehen ist, übernimmt er die Kontrolle. Anfänglich kann es zu Kommunikationsproblemen zwischen »gdb« und Target kommen. Es empfiehlt sich in diesem Fall, den Debugger zu beenden – beispielsweise indem man auf einer anderen Konsole das Kommando »sudo killall gdb« absetzt. Danach startet man »gdb« erneut und verbindet ihn mit dem KGDB des Targets.

Auf dem Target startet Root mit dem Kommando »echo g > /proc/sysrq-trigger« den Debugmodus. Zum Testen kann er sich beispielsweise mit dem GDB-Kommando »bt« den Stacktrace anzeigen lassen und per »break sys_umask« einen Breakpoint auf den Systemcall »umask()« setzen. Auf das Kommando »continue« gibt der GDB die Kontrolle wieder an den Kernel zurück. Kommt auf dem Debug-Target das Kommando »umask« zum Aufruf, führt das nun zum gewünschten Einfrieren des Systems und der Debugger erhält die Kontrolle.

Darüber hinaus bietet der GNU-Debugger in Kombination mit dem KGDB noch eine ganze Reihe weiterer Möglichkeiten. Wer den Linux-Kernel beispielsweise mit der Kommandozeilenoption »kgdbwait« startet, kann ihn bereits beim Booten untersuchen.

Weitere Hinweise finden sich in der Dokumentation des Linux-Kernels [1] sowie insbesondere zum Einsatz des GDB beim Kerneldebuggen auch in der Kern-Technik-Folge 60 [2]. (mhu)

Infos

  1. Jason Wessel, “Using kgdb, kdb and the kernel debugger internals”: https://www.kernel.org/doc/htmldocs/kgdb.html
  2. Quade, Kunst, “Kern-Technik”, Folge 60, Linux-Magazin 01/12, S. 104

Der Autor

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. In der Zwischenzeit ist die dritte Auflage ihres Buches “Linux-Treiber entwickeln” erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben