Zu den Basisoperationen eines Debuggers zählt das Einfrieren von Codesequenzen und das nachfolgende Analysieren von Speicherinhalten. Gehören die Codesequenzen zu einer Applikation, ist Debugging vergleichsweise unproblematisch. Wird aber der Kernel selbst eingefroren, steht keine Ablaufumgebung mehr zur Verfügung, die Tastatureingaben entgegennimmt, Daten auf den Monitor zaubert, auf Speicherinhalte zugreift oder die den Kernel später weiterarbeiten lässt. Kernel-Debugging hat etwas von einem Arzt, der sich selbst operieren möchte.
Technisch ist das Problem einfach zu lösen, indem man komplexe Funktionen auf ein zweites System auslagert. Das hat ja üblicherweise ein funktionierendes Speicher- und Dateimanagement und übernimmt das Durchwühlen des Quellcodes nach Variablen, Datenstrukturen, Funktionen und Codezeilen. Für den zu debuggenden Kernel wird nur noch ein so genannter Debugserver benötigt, der nur einfache Kommandos wie etwa Speicherzellen lesen und schreiben oder Breakpoints setzen in dem zu untersuchenden System ausführt.
Der in der vorigen Kern-Technik [1] vorgestellte Emulator Qemu enthält einen solchen Debugserver (siehe Kasten "Varianten des Kernel-Debugging"). Kommt zudem wieder die Systemgenerierungs-Software Buildroot [2] zum Einsatz, ist Kernel-Debugging vergleichsweise leicht umzusetzen – Voraussetzung ist ein mit Symbolinformationen ausgestatteter Kernel.
Dank Buildroot ist das aber kein Problem: Binnen Kurzem stellt das Tool ein übersichtliches Userland und einen schlanken Kernel zur Verfügung, der sich rasch umkonfigurieren und bearbeiten lässt. Alle Schritte sind in dem Kasten "Kurzanleitung" zusammengefasst. Zunächst lädt der Anwender Buildroot herunter und entpackt das Archiv. Danach erstellt er die Default-Konfiguration – diesmal am besten für ein x86-System – mit »make qemu_x86_defconfig«
.
Kernelquellen und GCC sowie Buildroot, Qemu und GDB sind die Zutaten für erfolgreiches Kernel-Debugging. Die folgende Anleitung fasst wie ein Kochrezept alle Arbeitsschritte für das richtige Setup zusammen. Die Feinheiten beschreibt der Artikel.
1. Buildroot herunterladen und auspacken
wget http://buildroot.uclibc.org/download/buildroot-2011.08.tar.bz2
tar xvfj buildroot-2011.08.tar.bz2
2. Buildroot konfigurieren und System generieren
cd buildroot-2011.08
make qemu_x86_defconfig
make menuconfig
[build-option, Kernel-Version 3.1, gdb, tty1]
make
make linux-menuconfig
[debug info]
make
3. Modul kompilieren und ins Rootverzeichnis kopieren
cd driver
export CROSS_COMPILE=...
export ARCH=...
make
cp module.ko .../output/target/root/
4. Root-Dateisystem neu generieren
cd buildroot-2011.08
make
5. System mit Qemu und Debugserver starten
qemu -kernel output/images/bzImage -hda output/images/rootfs.ext2 -append "root=/dev/sda rw" -s -S &
6. Debugger starten
gdb
file vmlinux
target remote :1234
continue
7. Einloggen, Treiber laden und Speicheradressen identifizieren
insmod Modul.ko
cat /sys/module/hello/sections/.text
cat /sys/module/hello/sections/.data
cat /sys/module/hello/sections/.bss
8. Modul-Symbolinformationen laden
add-symbol-file Modul.ko Addr_text
-s .data Addr_data
-s .bss Addr_bss
Vier Optionen sind nach dem anschließenden Aufruf von »make menuconfig«
anzupassen: Unter »Toolchain«
aktiviert der Anwender die Option »Build gdb for the Host«
, unter »Kernel | Kernel version«
gibt er »3.1«
ein, unter »System Configuration | Port to run getty (login prompt) on«
trägt er »tty1«
ein und unter »Build options | Number of jobs to run simultaneously«
, die Anzahl der Prozessorkerne der Generierungsmaschine.
Ein weiteres »make«
stößt den ersten Generierungslauf an, er installiert unter anderem die Kernelquellen, deren Konfiguration der Benutzer allerdings später noch einmal bezüglich Kernel-Debugging anpassen muss.
Debugging-Kernel
Dazu ruft der Anwender im Buildroot-Hauptverzeichnis »make linux-menuconfig«
auf. Die relevanten Einstellungen im daraufhin angezeigten Menü »Optionen«
befinden sich unterhalb des Punktes »Kernel hacking«
(siehe Abbildung 1). Erforderlich sind die Optionen »Kernel debugging«
und »Compile the kernel with debug info«
. Ein weiteres »make«
generiert einen Kernel mit der angepassten Konfiguration.
Abbildung 1: Die zum Debugging notwendigen Kerneloptionen finden sich unter dem Menüpunkt Kernel hacking.
Das Ergebnis sind im Wesentlichen zwei Dateien: Im Hauptverzeichnis findet sich die Datei »vmlinux«
, die nicht nur den Code, sondern auch die zugehörigen Debuginfos enthält. Im Architektur-Unterverzeichnis – für die x86-Plattform ist das beispielsweise »arch/x86/boot/«
– liegt der komprimierte Kernel als »bzImage«
. Auf anderen Plattformen heißt der Kernel schon mal »zImage«
.
Bootloader wie beispielsweise Grub brauchen den komprimierten Kernel (»bzImage«
), der Debugger selbst benötigt zwar ebenfalls das Image des Kernels, greift jedoch auf das unkomprimierte und mit Debuginfo versehene Pendant »vmlinux«
zurück. Selbstverständlich benötigt der Debugger auch Zugriff auf den Quellcode.
Wer mit Buildroot Kernel und Root-Filesystem generiert hat, sollte beides zunächst ohne Debugging ausprobieren:
qemu -kernel output/images/bzImage-hda output/images/rootfs.ext2-append "root=/dev/sda rw"
Funktioniert alles, kann das Debuggen durch Anhängen von »-s«
und »-S«
beginnen. Die Option »-s«
startet den Debugserver (»gdbserver«
), »-S«
hält den Kernel gleich zu Beginn an:
qemu -kernel output/images/bzImage-hda output/images/rootfs.ext2 -append"root=/dev/sda rw" -s -S
Damit der GNU-Debugger (GDB) die zum Kernel gehörenden C- und Headerdateien findet, startet der Anwender das Tool aus dem Quellcodeverzeichnis des Linux-Kernels heraus (siehe Abbildung 2). Wer ein x86-System gebaut hat, kann den auf dem Entwicklungsrechner vorinstallierten GNU-Debugger einsetzen, ansonsten kommt der für das Hostsystem gebaute und unterhalb des Buildroot-Verzeichnisses »output/host/usr/bin/«
abgelegte GDB zur Verwendung:
Abbildung 2: Der GDB wird aus dem Linux-Quellcodeverzeichnis gestartet, damit hat er Zugriff auf die C-Dateien.
cd output/build/linux-3.1/
gdb
Das Kommando »gdb«
startet die Debugger-Sitzung. Als Erstes lädt der Benutzer Code und Symbole des Kernels mit Hilfe des Befehls »file vmlinux«
. Wer nun die Meldung »no debug-symbols found«
zu sehen bekommt, muss die Kernelkonfiguration bezüglich der Debug-Optionen überprüfen und gegebenenfalls den Linux-Kernel neu kompilieren. Inklusive der Symbole ist »vmlinux«
mehr als 40 MByte groß.
Stopp und Start
Die Verbindung zum Debugserver stellt das Kommando »target remote :1234«
her (Abbildung 2). Danach kontrolliert »gdb«
die weitere Ausführung. Ein »continue«
aktiviert das Linux-Gastsystem, das Tastenkürzel [Strg]+[C] unterbricht die Abarbeitung. Abbildung 2 zeigt, wie das GDB-Kommando »break vfs_mknod«
einen Breakpoint auf die Funktion »vfs_mknod«
setzt.