en / de
AI
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

Linux Kernel Debugging mit JTAG und KGDB

In der Entwicklung von Embedded-Linux-Systemen ist das Verstehen des Kernelverhaltens essenziell – insbesondere bei schwer nachvollziehbaren Fehlern, Bootproblemen oder Abstürzen. Klassische Debuggingmethoden im User-Space stossen hier schnell an ihre Grenzen.

In diesem Projekt wurden zwei Methoden zur Analyse und zum Debugging des Linux-Kernels und der Module untersucht:

Verwendete Hardware:

Verwendete Software:

Setup Kernel:

Die Nutzung von Kernel-Debugging-Funktionalitäten, wie etwa KGDB, GDB-Skripte oder USB-Debugging, setzt die Verwendung des offiziellen Raspberry Pi Kernel (branch: linux-rpi-6.6.y) von GitHub voraus. Dieser Kernel wurde heruntergeladen und speziell konfiguriert, um die genannten Funktionalitäten zu ermöglichen.

Die folgenden Punkte müssen aktiviert werden, um den Kernel zu debuggen:

Folgende Optionen wurden in der Kernel-Konfiguration aktiviert (make menuconfig):

Anschliessend wurde der Kernel gebildet und installiert mit.

# Vorbereitung
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig

# Optional: Menü für eigene Konfiguration öffnen
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

# Kompilieren des Kernels und der Module
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image modules dtbs

# Installation der Module auf die SD-Karte (rootfs)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=/media/$USER/rootfs modules_install

# Erstellen der Hilfsscripts für den gdb
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- scripts_gdb 
# Kernel Image auf die Boot-Partition kopieren
cp arch/arm64/boot/Image /media/$USER/boot/kernel8.img

Im Rahmen der obigen Konfiguration wurden beide Debugmethoden getestet. Die Selektion der zu evaluierenden Methoden wurde durch das Config-File sowie durch die Parameter der Kernel-Command-Line determiniert.

Debugging JTAG

JTAG Debugging Setup

Abbildung: Anschlussdiagramm für JTAG-Debugging

Aktivierung serieller Konsole

Die Aktivierung der seriellen Konsole auf dem Raspberry Pi wurde durch eine Anpassung der Kernel-Commandline realisiert. Diese befindet sich in der Datei /boot/firmware/cmdline.txt, in welcher folgende Einträge hinzugefügt wurden:

console=ttyS0,115200 Die serielle Konsole auf dem UART ttyS0 wird aktiviert, wobei eine Baudrate von 115200 verwendet wird. Kein Risiko
rodata=off Standardmässig markiert der Linux-Kernel viele Datenbereiche als schreibgeschützt (read-only), um Sicherheitslücken zu vermeiden. Durch die Aktivierung dieser Option wird der Schreibschutz des Systems aufgehoben, was eine Voraussetzung für die Setzung von Breakpoints ist. Es sei darauf hingewiesen, dass diese Option die Systemsicherheit reduziert und daher ausschliesslich in einer Debugging-Umgebung verwendet werden sollte.
nokaslr Diese Funktion deaktiviert Kernel Address Space Layout Randomization (KASLR). Diese Funktion ist essenziell, um eine Adressauflösung zu gewährleisten, die ansonsten nicht möglich wäre. Es sei darauf hingewiesen, dass diese Option die Systemsicherheit reduziert und daher ausschliesslich in einer Debugging-Umgebung verwendet werden sollte.

Aktivierung JTAG Schnittstelle

Die Aktivierung der JTAG-Schnittstelle auf dem Raspberry Pi 4 erfordert die Hinzufügung der Parameter «enable_jtag_gpio=1» und «gpio=22-27=a4» in die Konfigurationsdatei «/boot/firmware/config.txt» unter «[all]».

openOCD

Für das Debugging des Raspberry Pi 4 über JTAG wurde die angepasste OpenOCD-Version von Raspberry Pi verwendet (https://github.com/raspberrypi/openocd). Der Build und die Ausführung erfolgten gemäss der offiziellen Anleitung, jedoch ohne die Installation. Stattdessen erfolgte die Ausführung lokal mit dem Befehl:

src/openocd -c "set USE_SMP 1" -f tcl/interface/ftdi/olimex-arm-usb-ocd-h.cfg -f tcl/target/bcm2711.cfg

Die folgende Erklärung behandelt die Parameter:

-c «set USE_SMP 1» Diese Option aktiviert den SMP-Modus (Symmetric MultiProcessing) für OpenOCD.
Es ist essentiell, dass dieser Befehl vor der Target-Definition steht. Andernfalls würde jede CPU des Raspberry Pi 4 einzeln als separates GDB-Target instanziert.
-f tcl/interface/ftdi/olimex-arm-usb-ocd-h.cfg Dient der Definition der Konfiguration für den verwendeten JTAG-Adapter (in diesem Fall: Olimex ARM-USB-OCD-H).
-f tcl/target/bcm2711.cfg Definiert das Target-Konfigurationsfile für den Broadcom BCM2711 SoC des Raspberry Pi 4.

Setup KGDB

KGDB Debugging Setup

Abbildung: Anschlussdiagramm für KGDB-Debugging

Der auf der Abbildung dargestellte Switch ist lediglich eine symbolische Darstellung. Der KGDB ist nämlich nur aktiv, wenn der Raspberry Pi 4 gestoppt ist. Läuft das System hingegen, ist KGDB deaktiviert und die serielle Konsole belegt den UART. Daher ist es auch nicht möglich, den Raspberry Pi 4 durch ein Stoppsignal (z. B. Ctrl+c) über GDB anzuhalten. Stattdessen wird der kgdb aktiviert, wenn auf dem Raspberry Pi 4 echo g > /prog/sysrq_trigger ausgeführt wird. Für die Ausführung dieses Befehls ist es essenziell, dass der Anwender Root ist. Die Verwendung des sudo-Kommandos ist hierfür nicht ausreichend.

Aktivierung serieller Verbindung

Die Aktivierung der seriellen Konsole und des KGDB auf dem Raspberry Pi wurde durch eine Anpassung der Kernel-Commandline realisiert. Diese befindet sich in der Datei /boot/firmware/cmdline.txt, in welcher folgende Einträge hinzugefügt wurden:

console=ttyS0,115200 Die serielle Konsole auf dem UART ttyS0 wird aktiviert, wobei eine Baudrate von 115200 verwendet wird. Kein Risiko
rodata=off Standardmässig markiert der Linux-Kernel viele Datenbereiche als schreibgeschützt (read-only), um Sicherheitslücken zu vermeiden. Durch die Aktivierung dieser Option wird der Schreibschutz des Systems aufgehoben, was eine Voraussetzung für die Setzung von Breakpoints ist. Achtung: Diese Option schwächt die Sicherheitsarchitektur des Systems und ist ausschliesslich für den Einsatz in dedizierten Debug-Setups vorgesehen.
nokaslr Diese Funktion deaktiviert Kernel Address Space Layout Randomization (KASLR). Diese Funktion ist essenziell, um eine Adressauflösung zu gewährleisten, die ansonsten nicht möglich wäre. Achtung: Diese Option schwächt die Sicherheitsarchitektur des Systems und ist ausschliesslich für den Einsatz in dedizierten Debug-Setups vorgesehen.
kgdbwait Der vorliegende Parameter führt dazu, dass der Kernel beim Start angehalten wird und auf eine Verbindung mit einem Debugger wie GDB wartet. Das System ausschliesslich in einer kontrollierten Debug-Umgebung verwenden, da bei der Ausführung ohne Debugger-Verbindung das System nicht fortfährt.
kgdboc=ttyS0,115200 Aktiviert das Kernel-Debugging über eine serielle Schnittstelle (hier ttyS0 mit 115200 Baud). Dies ermöglicht es, mit GDB über eine serielle Verbindung mit dem Kernel zu kommunizieren. Es wird empfohlen, dass das gleiche Gerät wie bei der seriellen Konsole verwendet wird. Achtung: Diese Option schwächt die Sicherheitsarchitektur des Systems und ist ausschliesslich für den Einsatz in dedizierten Debug-Setups vorgesehen.

agent-proxy

Da beide Outputs auf dem Raspberry Pi 4 über dieselbe serielle Verbindung geschaltet sind, muss diese auf dem PC wieder geteilt werden. Zu diesem Zweck kann ein einfacher Proxy (der bereits älterer Datums ist) Verwendung finden. Der Proxy verbindet sich mit dem ttyUSBx und generiert zwei Ports auf dem Localhost. Ein Port dient der Verbindung mit dem GDB, während der andere für die Verbindung mit der serielle Konsole über Telnet(welcome to stoneage) vorgesehen ist. Es sei jedoch darauf hingewiesen, dass dies zu Unannehmlichkeiten führen kann. Wenn der GDB aktiv ist (Target gestoppt), wird das gdb protokoll angezeigt.

Die Quelle für den Proxy ist unter https://github.com/xypron/agent-proxy zu finden. Die Installation und Konfiguration erfolgen gemäss der bereitgestellten Git-Anleitung.

 

Debugging

Zunächst ist ein Wechsel in das Root-Verzeichnis des Kernel-Builds erforderlich – also jenes Verzeichnis, in dem sich die Datei vmlinux (nicht vmlinuz!) befindet. Diese Datei enthält den unkomprimierten Kernel mit vollständigen Symbolinformationen und bildet die Grundlage für das Debugging.

Im Anschluss wird GDB gestartet, und zwar mittels der folgenden Befehlszequenz:

gdb-multiarch vmlinux

Beim Start sollten automatisch die GDB-Hilfsskripte (vmlinux-gdb.py) geladen werden. Zur Kontrolle kann innerhalb von GDB der Befehl «apropos lx» verwendet werden, wodurch alle verfügbaren lx_*-Hilfsfunktionen angezeigt werden, z. B. lx-task, lx-dmesg, lx-list-modules usw.

In GDB wird die Verbindung zur Debug-Hardware mittels des folgenden Befehls hergestellt target remote localhost:<PORT>. Beim Start von OpenOCD oder dem agent-proxy im Terminal ist der Port sichtbar (Standard: 3333 OpenOCD und beim agent-proxy als parameter mitegegeben ).

Sobald die Verbindung steht, kann mit dem Debugging begonnen werden. Natürlich kann der Debugger wie gewohnt in die IDE integriert werden.

Eine Besonderheit des KGDB ist, dass, falls noch keine Verbindung zum GDB besteht, der KGDB über das KDB-Interface direkt über die serielle Konsole gesteuert werden kann. Dies beinhaltet u.a. das Steppen und Weiterfahren.

Module Nachladen

Standardmässig sind dem GDB Kernel-Module, die dynamisch zur Laufzeit geladen werden, nicht bekannt. Um in einem solchen Fall eine Debugging-Sitzung innerhalb des Moduls zu ermöglichen, müssen die Symbolinformationen manuell nachgeladen werden.

Um GDB über das geladene Modul zu informieren, kann folgender Befehl verwendet werden:

add-symbol-file <pfad-zur-ko-datei> <.tx-adresse> [-s .bss <adresse> -s .data <adresse>]

# Bsp:
add-symbol-file ../../testproject/test.ko 0xffffff123456 
# oder
add-symbol-file ../../testproject/test.ko 0xffffff123456 -s .bss 0xffffff123456 -s .data 0xffffff123456

Hinweis:

Die entsprechenden Adressen können direkt auf dem Zielsystem, beispielsweise einem Raspberry Pi, gesammelt werden.

Unter /sys/module/<module_name>/sections sind drei Files von essenzieller Bedeutung: .text, .bss und .data, wobei die beiden Parameter .bss und .data optional sind. Anschliessend sind diese Adressen als Parameter bei «add-symbol-file» anzugeben. Der Parameter «.text» enthält den eigentlichen ausführbaren Code des Moduls. Die beiden anderen Dateien enthalten uninitialisierte bzw. initialisierte globale Daten.

Fazit

KGDB

Vorteile:

Eine der grössten Stärken von KGDB ist das Debugging bei einem Kernel-Panic. Wenn KGDB aktiviert ist, wechselt der Kernel im Falle eines Panics automatisch in einen Breakpoint. Dadurch kann mithilfe von Backtrace und einzelnen Stack-Frames der genaue Auslöser der Panic nachverfolgt werden.

Ein weiterer Vorteil ist der vergleichsweise geringe Verkabelungsaufwand. KGDB nutzt die serielle Konsole – diese ist bei vielen Systemen bereits nach aussen geführt, während ein dediziertes JTAG-Interface häufig nicht verfügbar oder nur schwer zugänglich ist. So lässt sich KGDB ohne zusätzliche Hardware direkt über die bestehende serielle Verbindung einsetzen.

Nachteile:

Die grösste Herausforderung beim Einsatz von KGDB ist das Steppen durch den Code. Bei jedem Step wird zunächst geprüft, ob Interrupts abgearbeitet werden müssen – häufig springt der Debugger daher in eine IRQ-Routine, statt zur nächsten Zeile des zu untersuchenden Codes. Abhilfe schafft hier nur mit Breakpoints und «continue» zu arbeiten.

Der Versuch, Interrupts zu deaktivieren, war in der Praxis oft nicht erfolgreich. Zudem kann das vollständige Deaktivieren von Interrupts problematisch sein, da die serielle UART-Verbindung selbst auf Interrupts angewiesen ist. Werden diese nicht mehr verarbeitet, kann keine weitere Kommunikation über die Konsole erfolgen – der Debugger «hängt» und der Kernel ist nicht mehr steuerbar.

JTAG

Vorteile

Debugging kann bereits bei der allerersten Codezeile oder sogar im Bootloader beginnen. Voraussetzung ist das Setzen eines Breakpoints direkt im Quellcode vor dem Kompilieren.

Steppen im Code funktioniert zuverlässig, da IRQs automatisch von OpenOCD bzw. GDB deaktiviert werden und somit keine ungewollten Interrupts während des Debuggens auftreten.

Nachteile

Erfordert eine physikalische Verbindung zum JTAG-Interface. Dies setzt passende Debug-Hardware sowie Zugriff auf die entsprechenden Pins (z. B. via GPIO oder Debug-Header) voraus.

Der JTAG-Zugriff muss durch das Board-Design unterstützt werden. Bei vielen Systemen ist das Interface nicht nach aussen geführt oder per Software deaktiviert.

Je nach SoC ist eine angepasste Version von OpenOCD sowie eine spezifische Konfiguration notwendig, was die Einrichtung deutlich aufwendiger macht.

Empfehlung

Wenn ein funktionierender JTAG-Zugang verfügbar ist, sollte dieser bevorzugt genutzt werden – insbesondere aufgrund der komfortablen Stepping-Möglichkeiten und des vollständigen Systemzugriffs.
In allen anderen Fällen, insbesondere wenn kein JTAG vorhanden oder zugänglich ist, stellt KGDB eine Alternative dar, um mit vergleichsweise geringem Aufwand Kernel-Debugging durchzuführen.

Bereits bei der Hardwareentwicklung sollte darauf geachtet werden, dass der JTAG-Zugang nach außen geführt wird, selbst wenn er im späteren Produkt nicht dauerhaft genutzt wird. Dies erleichtert die Fehlersuche während der frühen Entwicklungs- und Integrationsphase erheblich.

 

Kommentare

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden

Copyright © 2025 Noser Engineering AG – Alle Rechte vorbehalten.

NACH OBEN
Privacy Policy Cookie Policy
Zur Webcast Übersicht