Remote Debugging von Embedded-Linux-Systemen mit GDB

Aktualisiert am 26.04.2023 Von Jan Altenberg |

Anbieter zum Thema

Software, die auf Embedded Systemen zum Einsatz kommt, wird in der Regel nicht auf diesen entwickelt. Trotzdem muss sie zuverlässig geprüft und von Fehlern befreit werden. In Linux ist hierfür bereits ein nützliches Tool integriert: Der GNU Universal Debugger (GDB).

Auf einem Embedded-Linux-System direkt lässt sich Software aufgrund der begrenzten Ressourcen nur schlecht debuggen. Das in Linux integrierte Tool GDB erlaubt, die Embedded Software über einen PC extern auf Fehler zu untersuchen.
Auf einem Embedded-Linux-System direkt lässt sich Software aufgrund der begrenzten Ressourcen nur schlecht debuggen. Das in Linux integrierte Tool GDB erlaubt, die Embedded Software über einen PC extern auf Fehler zu untersuchen.
(Bild: Clipdealer)

Fehlerfreie Software gibt es nicht! Wer Software entwickelt ist früher oder später immer damit konfrontiert, Fehler zu suchen, einzugrenzen und zu beheben. Fehler in Software existieren seit es Systeme zur elektronischen Datenverarbeitung gibt. Und sogar analoge Rechensysteme verhielten sich nicht immer korrekt: Es wird erzählt, dass sich in den frühen Tagen der Datenverarbeitung ab und an Käfer in die Schaltung eines Großrechners verirrt haben und einen Kurzschluss verursachten. Dies hatte zur Folge, dass der Großrechner seine Aufgabe nicht immer korrekt erfüllen konnte. Wir sprechen heute also nicht ohne Grund von einem „Bug“, wenn sich ein Fehler in die Software oder auch in die Hardware eingeschlichen hat. Waren es früher tatsächlich Käfer, die sich in eine Schaltung verirrten, so ist es heute die zunehmende Komplexität von Software, die die Fehleranfälligkeit während des Entwicklungsprozesses begünstigt. Fakt ist: Fehlerfreie Software gibt es nicht! Entsprechende Werkzeuge zur Fehlersuche sind daher unabdingbar!

SEMINAR-TIPP

Embedded-Linux-Woche

Embedded-Linux-Woche

In den Einsteigerkursen der Embedded-Linux-Woche führen unsere Referenten Sie Schritt für Schritt in die Linux-Welt ein. Die Basis bildet der Grundlagen-Kurs. Im darauf aufbauenden Seminar "Embedded Linux" kann das Gelernte vertieft werden. Erlangen Sie in nur fünf Tagen das notwendige Wissen, um vom Linux-Neuling zum Fortgeschrittenen zu werden!

Weitere Details und Termine

Herausforderungen beim Embedded-Systems-Debugging

Unter Linux kann der Anwender auf eine Vielzahl von Werkzeugen zum Debuggen von Applikationen zurückgreifen. Das bekannteste Beispiel hierfür ist der GNU Debugger (GDB). Der von GDB bereitgestellte Funktionsumfang steht dem kommerzieller Systeme in nichts nach. So können Breakpoints gesetzt, Speicherbereiche und parallele Programmflüsse analysiert und jegliche Aspekte des Programmablaufes betrachtet werden. Weiterhin werden von GDB alle im industriellen Sektor gängigen CPU Architekturen unterstützt.

Doch gerade die Tatsache, dass bei der Entwicklung von Embedded Systemen unterschiedliche CPU Typen zum Einsatz kommen, stellt an das Debugging eine besondere Anforderung: Die Software wird üblicherweise nicht auf dem System entwickelt und gedebuggt, auf dem sie zum Einsatz kommt. Es muss also die Möglichkeit bestehen, von einem Entwicklungssystem, ein Programm zu untersuchen, während es direkt auf dem Zielsystem ausgeführt wird; und das möglichst transparent. Im Idealfall soll der Entwickler beim Debuggen gar nicht merken, dass die Software auf einem anderen System ausgeführt wird. Das hier beschriebene Szenario wird allgemein als „Remote Debugging“ bezeichnet. GDB bietet hierfür bereits umfangreiche Unterstützung!

Setup einer Remote- Debugging-Umgebung mit GDB

Das Remote Debugging einer Applikation mit GDB ist nicht viel schwerer als der Umgang mit einer nativen Applikation. Allerdings gilt es beim Aufsetzen einer entsprechenden Umgebung einige Dinge zu beachten. Die wichtigste Anforderung an das Zielsystem ist ein Hilfsprogramm mit dem Namen gdbserver. Mit dem gdbserver kann über ein definiertes Protokoll wahlweise per serieller Schnittstelle oder per Ethernet kommuniziert werden. Aufgrund der Performanceeinschränkungen der seriellen Schnittstelle empfiehlt sich grundsätzlich das Debugging per Netzwerk. Daher ist es immer ratsam für den Entwicklungsprozess eine Netzwerkschnittstelle vorzusehen, auch wenn diese im Produkt später nicht benötigt wird!

Üblicherweise wird mit der Toolchain für das jeweilige Target auch ein passendes gdbserver Executable mitgeliefert. Meist befindet sich dieses Executable in der Verzeichnisstruktur der Toolchain innerhalb der sogenannten „sysroot“, also dem Bereich, wo auch die Bibliotheken für das Zielsystem zu finden sind. Auf Debian basierten Systemen (wie zum Beispiel dem Raspberry PI) kann der gdbserver ganz einfach über das Paketmanagement nachinstalliert werden: apt-get install gdbserver.

Doch egal auf welche Weise gdbserver installiert wird, es gilt dabei folgendes zu beachten: GDB (also der Debugger) und gdbserver müssen unbedingt zusammenpassen. Das bedeutet, wenn Sie auf dem Zielsystem gdbserver von Debian verwenden, dann sollte auch auf dem Entwicklungssystem der passende GDB von Debian zum Einsatz kommen.

Gleich verhält es sich mit einer beliebigen Toolchain: Wird der gdbserver der Toolchain verwendet, so muss für das Debugging der zugehörige GDB (ebenfalls Bestandteil der Toolchain) verwendet werden. Diese Anforderung ist nicht ganz offensichtlich, denn in der Theorie wird über ein standardisiertes Protokoll kommuniziert. Die Praxis zeigt aber, dass es zu massiven Problemen kommen kann, wenn die Versionen von GDB und gdbserver zu weit auseinander laufen. Typische Fehler sind hier zum Beispiel Kommunikationsabbrüche mit der Meldung „Unexpected end of file“ oder Ähnliches.

Weiterhin muss natürlich die Möglichkeit bestehen, die zu untersuchende Applikation auf das Target zu kopieren. Auch hier empfiehlt sich wieder eine Netzerkverbindung; als Protokoll hat sich für diesen Zweck SSH etabliert. Mit SSH können nicht nur Daten von A nach B kopiert werden, sondern hauptsächlich auch Kommandos remote ausgeführt werden. Das Ausführen von Kommandos auf dem Zielsystem ist notwendig, da die Applikation dort gestartet werden soll. Weiterhin kann mit SSH der Ablauf einer Debug Session auch sehr einfach automatisiert werden. Auf Embedded Systemen kommt sehr häufig dropbear zum Einsatz.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Dropbear ist eine leichtgewichtige SSH Implementierung für eingebettete Systeme. Grundsätzlich werden alle Funktionen unterstützt, die für das Remote Debugging erforderlich sind. Doch leider beherrscht dropbear für das Kopieren von Daten nur das sogenannt scp Protokoll. Für die Integration einer Remote Debugging Umgebung in Eclipse wird allerdings das sftp Protokoll vorausgesetzt. Sofern eine Integration in eine grafische Oberfläche erforderlich ist, empfiehlt sich auf dem Zielsystem daher die Verwendung des OpenSSH Paketes, welches auch das sftp Protokoll unterstützt.

Ein weiterer wichtiger Punkt, der eine der häufigsten Fehlerquellen beim Remote Debugging darstellt, ist der Zugriff des Debuggers auf die passenden Bibliotheken. Der gdbserver ist lediglich ein Hilfsprogramm, welches Informationen an das Entwicklungssystem sendet bzw. Kommandos entgegennimmt und ausführt. Jegliche Debugging Logik findet auf dem Entwicklungssystem statt. Das heißt, wenn nun auf dem Zielsystem eine Bibliothek nachgeladen wird, so wird diese Information an den Debugger weitergeleitet. Dieser muss dann die entsprechende Bibliothek ebenfalls nachladen. Somit muss der Debugger Zugriff auf genau die Bibliotheksversionen haben, wie sie auf dem Target zum Einsatz kommen.

Bild 1: Systematischer Aufbau eines Remote Debugging Setups.
Bild 1: Systematischer Aufbau eines Remote Debugging Setups.
(Bilder: Linutronix)

Bild 1 zeigt den grundsätzlichen Aufbau einer Remote Debugging Umgebung. Um GDB mitzuteilen, wo die Bibliotheken für das Zielsystem zu finden sind, kann die Einstellung „solib-absolute-prefix“ oder „sysroot“ verwendet werden. Der dort angegebene Pfad ist üblicherweise die Sysroot der verwendeten Toolchain. Weiterhin besteht die Möglichkeit, Bibliotheken direkt vom Target nachzuladen. Letzteres ist in aktuellen GDB Versionen der Default, sofern kein Bibliothekspfad gesetzt wurde (wenn die Bibliotheken vom Target geladen werden, erscheint beim Aufbau der Verbindung eine entsprechende Meldung). Diese Funktionalität ist zwar sehr nützlich, allerdings ist zu bedenken, dass das Übertragen von Bibliotheken einiges an Bandbreite erfordert.

Eine Checkliste fürs Remote Debugging

Aus dem bisher beschriebenen Setup lässt sich eine kurze Checkliste für das Remote Debugging eines Systems erarbeiten:

  • 1. Ist das gdbserver Executable auf dem Zielsystem vorhanden und ausführbar?
  • 2. Passt das gdbserver Executable zum verwendeten GDB?
  • 3. Können Dateien auf das Zielsystem kopiert werden (scp oder sftp)?
  • 4. Können Kommandos remote auf dem Zielsystem ausgeführt werden (ssh)?
  • 5. Hat der Debugger Zugriff auf die auf dem Target verwendeten Bibliotheken?

Ablauf einer Remote- Debugging-Sitzung

Im Folgenden soll nun der konkrete Ablauf einer Debug Session beispielhaft für ein ARM basiertes Zielsystem aufgezeigt werden (es wird davon ausgegangen, dass die entsprechenden Dienste, wie SSH, auf dem Zielsystem vorhanden und konfiguriert sind).

Als Beispiel dient ein simples „Hallo Welt“ Programm (hello.c)

#include
int main(void)
{
    printf("Hallo Welt\n");
    return 0;
}

Im ersten Schritt ist die Applikation mit Debugsymbolen fürs Zielsystem zu erstellen:

arm-linux-gnueabihf-gcc -Wall -g -o hello hello.c

Das Compilerprefix kann sich je nach verwendeter Toolchain leicht unterscheiden, die Nomenklatur ist jedoch grundsätzlich immer die selbe: Das erste Feld bezieht sich auf die CPU Architektur (arm-), das zweite auf das Betriebssystem (linux-) und das dritte Feld auf die sogenannte ABI (gnueabihf-, also Hardware Floating Point). Mit der Option -g werden Debug Symbole erzeugt. Wichtig: Das Hinzufügen der Option -g ändert zunächst nichts an der Codegenerierung, es wird lediglich die Symboltabelle erzeugt. Sollen für das Debugging noch die Optimierungen deaktiviert werden, so wird zusätzlich noch die Option -O0 benötigt!

Nun muss das Executable auf das Zielsystem kopiert werden, dies kann zum Beispiel mit dem scp Kommando erfolgen:

scp hello user@192.168.1.11:/tmp

Mit diesem Kommando wird das Executable auf das Zielsystem mit der IP Adresse 192.168.1.11 und dort in den Ordner /tmp kopiert. Die Authentifizierung erfolgt über den Nutzer mit dem Namen„user“. Anschließend wird die Applikation auf dem Zielsystem mithilfe des gdbservers gestartet. Eine Anmeldung auf dem Zielsystem kann wieder per SSH erfolgen:

ssh user@192.168.1.11

Die Applikation wird dann wie folgt gestartet:

gdbserver 192.168.1.10:2345 /tmp/hello

Mit dem obenstehenden Aufruf wird der Zugriff aus dem Debugger für die Gegenstelle mit der IP Adresse 192.168.1.10 über die Portnummer 2345 erlaubt. Die Angabe der IP Adresse ist optional. Wird diese ausgelassen (also beispielsweise gdbserver :2345 /tmp/hello) so wird jeder Gegenseite der Zugriff erlaubt. Das Starten des Executables wird mit folgender Ausgabe bestätigt:

gdbserver :2345 /tmp/hello
Process /tmp/hello created; pid = 1249
Listening on port 2345

Nach dem Starten von gdbserver kann sich nun die Gegenseite mit dem Debugger verbinden. Hierzu wird auf dem Entwicklungssystem zunächst das Executable mit Debugsymbolen in GDB geladen:

arm-linux-gnueabihf-gdb hello

Es empfiehlt sich nach dem Laden der Applikation in GDB zu prüfen, ob die Symbole geladen werden konnten. Es sollte folgende Meldung erscheinen:

Reading symbols from hello...done.

An dieser Stelle muss nun entschieden werden, ob der Debugger die Bibliotheken vom Zielsystem oder lokal laden soll (das lokale Laden kann mit set solib-absolute-prefix /sysrootpfad konfiguriert werden). In diesem Beispiel wird der Default angenommen (also das Laden vom Zielsystem). Nun kann eine Verbindung zum Target aufgebaut werden, dies erfolgt mit dem „target“ Kommando:

target remote 192.168.1.11:2345

Bei erfolgreichem Verbindungsaufbau erscheint auf dem Zielsystem aus gdbserver eine entsprechende Ausgabe:

Remote debugging from host 192.168.1.10

Bild 2: Rückgabemeldungen auf dem Entwicklungssystem.
Bild 2: Rückgabemeldungen auf dem Entwicklungssystem.
(Bild: Linutronix)

Analog sind auf dem Entwicklungssystem die in Bild 2 gezeigten Meldungen zu sehen.

Unter anderem wird nochmals bestätigt, dass die Bibliotheken vom Zielsystem geladen werden. Sobald die Verbindung erfolgreich zustande kam, kann die Applikation wie jede native Applikation gedebuggt werden. Im weiteren Ablauf muss sich der Anwender also nicht mehr damit auseinandersetzen, dass das Executable auf einem anderen System ausgeführt wird. Es können alle gängigen GDB Kommandos verwendet werden!

Für das beschriebene „Hallo Welt“ Programm könnte man sich folgenden Ablauf vorstellen:

Setzen eines Breakpoints beim Einsprung in die main() Funktion:

(gdb) break main
Haltepunkt 1 at 0x10414: file hello.c, line 5.

Starten des Programms (gdbserver hat das Programm bereits gestartet und bei der ersten Instruktion gestoppt, daher wird nicht das „run“ Kommando verwendet):

(gdb) continue
Continuing.
Reading /lib/libc.so.6 from remote target...
Breakpoint 1, main () at hello.c:5
05    printf("Hallo Welt\n");

Single step (auf dem Target sollte nun die „Hallo Welt“ Ausgabe erscheinen):

(gdb) next

Beenden (Beim Beenden des Debuggers wird auch der gdbserver auf dem Zielsystem beendet):

quit

Zum wiederholten Starten der Debug Session müssen die beschriebenen Schritte erneut ausgeführt werden. Der komplette Vorgang kann weitestgehend automatisiert werden. GDB bietet die Möglichkeit Kommandos beim Start über ein Skriptfile ausführen zu lassen. Dies erfolgt über die Option -x:

arm-linux-gnueabihf-gdb -x gdbinit.txt hello

In gdbinit.txt kann nun zum Beispiel das „target remote“ Kommando aufgenommen werden, um automatisch eine Verbindung zum Zielsystem aufzubauen. Das Kopieren des Executables und das Starten des gdbservers kann über ein simples Shell Script erledigt werden (beispielsweise remote_debug.sh):

#!/bin/sh
scp hello user@192.168.1.11:/tmp
ssh user@192.168.1.11 '/usr/bin/gdbserver :2345 /tmp/hello'

Nun kann in einem Terminal mit ./remote_debug.sh das Target vorbereitet (Kopieren des Executables und Starten von gdbserver) und in einem anderen Terminal kann mit

arm-linux-gnueabihf-gdb -x gdbinit.txt hello

die Debug Session gestartet werden.

Integration in eine grafische Umgebung

Bild 3: Konfigurationsdialog für Remote Debugging in Eclipse.
Bild 3: Konfigurationsdialog für Remote Debugging in Eclipse.
(Bild: Linutronix)

Der komplette Ablauf für das Remote Debugging kann auch in eine grafische Oberfläche eingebettet werden. Eclipse beispielsweise unterstützt die Integration von GDB und gdbserver. Die Konfiguration hierfür ist weitestgehend selbsterklärend. Beim Anlegen einer Debug Konfiguration in Eclipse muss hierfür „C/C++ Remote Application gewählt werden“. Im entsprechenden Konfigurationsdialog kann nun eine Verbindung zum Zielsystem erstellt bzw. eine bereits konfigurierte Verbindung ausgewählt werden. Eine Verbindung beeinhaltet unter anderem die IP Adresse und die Login Credentials für das Zielsystem. Unter dem Reiter Debugger kann der zu verwendende GDB (in unserem Falle arm-linux-gnueabihf-gdb) angegeben werden. Sobald nun eine Netzwerkverbindung einem Zielsystem zugeordnet wurde, wird beim Starten der Debug Session aus Eclipse das Executable auf das Zielsystem kopiert, gdbserver gestartet und eine Verbindung mit GDB aufgebaut (Achtung: Für das Kopieren wird auf dem Target sftp vorausgesetzt!). Das Debugging kann dann aus der grafischen Oberfläche erfolgen.

Bild 3 zeigt den Konfigurationsdialog für eine Remote Debugging Session. Leider sind die Fehlermeldungen, falls eine Verbindung aus Eclipse heraus nicht erfolgreich ist, nicht immer einfach zu interpretieren. Sollte es Probleme bei der Ausführung geben, empfiehlt es sich daher, alle notwendigen Kommandos gemäß der beschriebenen Checkliste auf der Kommandozeile durchzuspielen, um die mögliche Fehlerquelle zu identifizieren. Grundsätzlich wird empfohlen, alle Teilschritte mindestens einmal manuell auszuführen, bevor eine Integration in Eclipse erfolgt.

Embedded-Linux-Woche: Programm und Anmeldung Auf der Embedded-Linux-Woche in Würzburg können Sie Ihr Wissen weiter vertiefen. Prämierte Referenten geben dort Seminare für Einsteiger, Fortgeschrittene und Experten zu verschiedenen Themen der Embedded Linux-Programmierung. Mehr Informationen zu Kursen und Anmeldung finden Sie auf www.linux4embedded.de.

Fazit: Besondere Anforderungen durch GDB abgedeckt

Das Debugging von Applikationen auf Embedded Systemen stellt besondere Anforderungen an die zu verwendenden Werkzeuge. Üblicherweise muss das Debugging remote von einem Entwicklungssystem erfolgen. Der GNU Debugger GDB bietet hierfür alle notwendigen Funktionen. Das Aufsetzen einer entsprechenden Umgebung ist unter Befolgung einiger einfacher Regeln äußerst einfach zu bewerkstelligen. Der Ablauf lässt sich weitestgehend automatisieren und sogar in eine grafische Entwicklungsumgebung integrieren.

Der Autor

Jan Altenbert, Linutronix
Jan Altenbert, Linutronix
(Bild: Linutronix)

* Jan Altenberg beschäftigt sich seit mehr als 15 Jahren beruflich mit Linux. Er betreute u.a. verschiedene Arbeiten für das EU-Projekt OCEAN, welches sich zum Ziel setzte, eine offene Steuerungsplattform auf Basis von Realtime Linux zu schaffen. Weiterhin trug er bei einem namhaften Maschinenhersteller die volle konzeptionelle Verantwortung für die Einführung von Linux in der neuen Steuerungsgeneration. Seit 2007 arbeitet er für die Linutronix GmbH und leitet dort derzeit den technischen Vertrieb. Jan Altenberg ist regelmäßiger Sprecher auf verschiedenen Fachkonferenzen und wurde 2014 von den Besuchern des ESE Kongress mit dem Speaker Award Publikumspreis ausgezeichnet.

Artikelfiles und Artikellinks

(ID:45686007)