Share this

Analyse und Verbesserung von GDBstub

2026-02-21 12:29:23 · · #1
Zusammenfassung: Dieser Artikel behandelt die Implementierung der GDB-Remote-Debugging-Technologie zum Debuggen von Kerneln und eingebetteten Systemen. Er beschreibt kurz den GDB-Hostrechner und das GDB-Remote-Serial-Protokoll und analysiert detailliert verschiedene Implementierungsmethoden des GDB-Debugging-Agenten auf Kernel- und Anwendungsebene. Es wird eine Methode zum Debuggen von Anwendungen ohne Modifikation des Betriebssystemkernels vorgeschlagen. Diese Methode ist hochgradig portabel und eliminiert die potenziellen Risiken, die mit der Modifikation des Systemkernels verbunden sind, wodurch der durch Kernelmodifikationen verursachte Arbeitsaufwand reduziert wird. Anwendungen beim Debuggen von Mikrokernel-Betriebssystemdiensten demonstrieren die Effektivität dieser Methode. Schlüsselwörter: Remote-Debugging; Stub; GDBserver; KGDB; Debugging eingebetteter Systeme. Zusammenfassung: Diese Arbeit behandelt die Realisierung der GDB-Remote-Debugging-Technologie in Kerneln und eingebetteten Systemen. Zunächst werden der GDB-Host und das GDB-Remote-Serial-Protokoll beschrieben, anschließend wird die Realisierung des GDB-Stubs auf Kernel- und Anwendungsebene detailliert analysiert. Abschließend präsentieren die Autoren eine neue Methode zum Debuggen von Anwendungen ohne Modifikation des Betriebssystemkernels. Diese Methode zeichnet sich durch hohe Portabilität aus, beseitigt die versteckten Probleme der Betriebssystemkernel-Modifikation und reduziert den damit verbundenen Arbeitsaufwand. Die Anwendung beim Debuggen von Betriebssystemdiensten in Mikrokernelsystemen zeigt, dass diese Methode angemessen effizient ist. Schlüsselwörter: Remote-Debugging; Stub; GDBserver; KGDB; Debugging eingebetteter Systeme. 1. Einleitung. Debugging ist ein wesentlicher Bestandteil des Entwicklungsprozesses. Das Debuggen von Kerneln und eingebetteten Systemen unterscheidet sich jedoch von traditionellen Debugging-Systemen. Eingebettete Systeme können typischerweise keinen lokalen Debugger verwenden, da: die Systemressourcen begrenzt sind; der Speicher klein ist; Ein-/Ausgabegeräte nicht für das Debugging verwendet werden können. Traditionelle Debugging-Systeme benötigen ein Dateisystem, das eingebetteten Systemen in der Regel fehlt, und das Kernel-Debugging unterstützt Dateisysteme noch nicht. Der Debugger selbst benötigt Unterstützung durch das Betriebssystem, was das Debuggen des Betriebssystemkernels verhindert. Die effektivste Lösung ist die Remote-Debugging-Technologie. Remote-Debugging bezeichnet eine Debugging-Technik, bei der die Betriebsumgebung des Debuggers (Host) und das zu debuggende System (Zielsystem) physisch getrennt sind und über eine serielle Schnittstelle oder ein Netzwerk verbunden werden. GDB, kostenlos von GNU bereitgestellt, bietet leistungsstarke Funktionen für das Remote-Debugging. Entwickler können damit Programmcode auf der Zielplattform schrittweise durchlaufen, Haltepunkte setzen, den Speicher untersuchen und Informationen remote mit der Zielplattform austauschen. Die Vorteile von GDB – Echtzeitfähigkeit, Dynamik, Benutzerfreundlichkeit und kostenlose Verfügbarkeit – haben es zunehmend zur bevorzugten Debugging-Lösung für die Entwicklung eingebetteter Systeme gemacht. Ein Remote-Debugging-System besteht aus drei Komponenten: einem lokalen Debugger auf dem Host, einem Debugging-Agenten auf dem Zielrechner und einem Remote-Debugging-Protokoll. Abbildung 1 zeigt die drei Komponenten eines GDB-Remote-Debugging-Systems: GDB, GDBstub und das GDB Remote Serial Protocol (RSP). Diese drei Komponenten werden im Folgenden analysiert. Abbildung 1: Remote-Debugging-System. Abbildung 2: RSP-Protokoll. Das GDB RSP (Remote Serial Protocol) definiert das Format der Datenpakete bei der Kommunikation zwischen dem GDB-Hostrechner und dem zu debuggenden Zielrechner. Das Informationsformat lautet: $data#checksum. Die meisten Informationen werden im ASCII-Code dargestellt. Die Daten bestehen aus einer Folge von ASCII-Codes, die Prüfsumme ist eine Ein-Byte-Prüfsumme aus zwei Hexadezimalzahlen. Der Empfänger empfängt die Daten und überprüft sie. Bei Korrektheit antwortet er mit „+“, bei Inkorrektheit mit „-“. Die Kommunikation umfasst Befehle zum Lesen und Schreiben von Daten, zur Steuerung der Programmausführung und zur Meldung des Programmstatus. Die grundlegenden RSP-Befehle lassen sich aus Sicht des Kommunikationsdialogs in zwei Typen unterteilen: 1) Anfrage?: Aktuellen Systemstatus lesen; g: Alle Register lesen; G: Alle Register schreiben; m: Speicher lesen; M: Speicher schreiben; c: Ausführung fortsetzen; s: Einzelschrittausführung; k: Prozess beenden. 2) Antwort: "": GDB mitteilen, dass der zuletzt angeforderte Befehl nicht unterstützt wird; E: GDB mitteilen, dass ein Fehler aufgetreten ist; OK: Die letzte Anfrage war korrekt; W: Das System beendet sich im Zustand exit_status; X: Das System wird aufgrund des Signals beendet; S: Das System wird aufgrund des Signals gestoppt; O: GDB anweisen, eine Ausgabe auf der Konsole zu tätigen. Dies ist der einzige Befehl, der an GDB gesendet wird. 3. GDB-Remote-Debugging-Funktion: Beim Debuggen des Kernels ist üblicherweise kein Dateisystem vorhanden, und die meisten eingebetteten Systeme verfügen aufgrund von Ressourcenbeschränkungen über kein Dateisystem. Daher werden Quelldateien, Objektdateien und Symboltabellen, die mit dem Dateisystem zusammenhängen, auf dem Host-Rechner gespeichert und vom Debugger auf dem Host verarbeitet. Auch die für das Debugging verwendeten Ein-/Ausgabegeräte werden vom Host bereitgestellt. Der Debugger auf dem Host-Rechner empfängt und verarbeitet die vom Benutzer eingegebenen Debugging-Befehle vor. Bei einigen Befehlen (z. B. Haltepunkten) erfolgt die Verarbeitung direkt auf dem Host-GDB, ohne dass eine Kommunikation mit dem Zielrechner erforderlich ist. Natürlich müssen weitere Anweisungen auf dem Debugging-Agenten auf dem Zielrechner implementiert werden. Der Host kapselt die vorverarbeiteten Befehle gemäß RSP und sendet sie an den Debugging-Agenten auf dem Zielrechner. Der Debugging-Agent empfängt die Befehle, verarbeitet sie entsprechend und sendet die Informationen an den Debugger auf dem Host-Rechner zurück. 4. Implementierung des Stubs auf dem Zielsystem: Die Hauptfunktion des Stubs auf dem Zielsystem besteht in der Kommunikation mit dem Host-GDB, um Speicher und Register zu lesen und zu beschreiben sowie den Vorgang anzuhalten und fortzusetzen. Abbildung 2 zeigt ein allgemeines Kommunikationsmodell zwischen dem Host-GDB und dem Stub auf dem Zielsystem: Abbildung 2. Allgemeines Kommunikationsmodell zwischen GDB und dem Stub auf dem Zielsystem. Zielsystem und Host sind hardwareseitig verbunden. Der zu debuggende Teil wird in den Stub eingefügt, und GDB kommuniziert über RSP mit diesem Teil. Je nachdem, in welcher Schicht sich der Stub befindet, werden verschiedene Debugging-Ebenen erreicht, darunter Kernel- und Anwendungsschicht-Debugging. 4.1 Debugging-Modell der Kernelschicht Abbildung 3. Kernel-Debugging mithilfe eines Stubs Wie in Abbildung 3 dargestellt, ermöglicht das Einfügen des Stubs in den Kernel das Kernel-Debugging. Der Linux-Kernel-Debugging-Mechanismus KGDB verwendet dieses Modell. KGDB kann in ein Initialisierungsmodul und ein Steuermodul unterteilt werden. 4.1.1 Initialisierungsmodul: Die Ausnahmebehandlungsfunktion wird so modifiziert, dass beim Auftreten einer Ausnahme die Funktion `handle_exception()` aufgerufen wird, wodurch GDB diese Ausnahmen abfangen kann. Nach der Initialisierung wird die Funktion `breakpoint()` verwendet, um die Systemsteuerung direkt an GDB zu übergeben. Die KGDB-Modifikationen der Ausnahmebehandlungsfunktion lassen sich grundsätzlich in zwei Typen unterteilen. Definition des Makros `CHK_REMOTE_DEBUG`: `#define CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,after) { if (linux_debug_hook != (gdb_debug_hook *) NULL && !user_mode(regs)) { (*linux_debug_hook)(trapnr, signr, error_code, regs) ; after;` } }  Ändern Sie den Programmablauf am Beispiel der int3-Verarbeitungsfunktion: #define DO_VM86_ERROR(trapnr, signr, str, name) asmlinkage void do_##name(struct pt_regs * regs, long error_code) { CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,goto skip_trap) do_trap(trapnr, signr, str, 1, regs, error_code, NULL); skip_trap: return; } Expand DO_VM86_ERROR (3,SIGTRAP,"int3",int3) asmlinkage void do_int3(struct pt_regs *regs, long error_code) { if (linux_debug_hook != ( gdb_debug_hook *)NULL&&! user_mode(regs)) { (*linux_debug_hook)(3, SIGTRAP, errorcode, regs); goto skip_trap; } do_trap(3, SIGTRAP, "int3", 1, regs, error_code, NULL); skip_trap: return; } Wie aus dem obigen Code ersichtlich, ist nach dem Eintritt in den Kernel-Debug-Zustand die Ausnahmebehandlungsfunktion handle_exception(), und der Programmablauf überspringt die Behandlungsfunktion do_trap im Nicht-Debug-Zustand. Ohne den Programmablauf zu ändern, betrachten wir am Beispiel der Ausnahmebehandlungsfunktion `divide_error` Folgendes: `#define DO_VM86_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) asmlinkage void do_##name(struct pt_regs * regs, long error_code) { …… do_trap(trapnr, signr, str, 1, regs, error_code, &info); }` Die Erweiterung von `DO_VM86_ERROR_INFO( 0, SIGFPE, "divide error", divide_error, FPE_INTDIV, regs->eip) asmlinkage void do_divide_error (struct pt_regs *regs, long error_code) { if (linux_debug_hook != ( gdb_debug_hook *)NULL&&! user_mode(regs)) { (*linux_debug_hook)(3, SIGTRAP, errorcode,` Der Codeausschnitt `do_trap(0, SIGTRAP, "divide erro", 1, regs, error_code, &info); }` verdeutlicht den Unterschied zwischen Debug- und Nicht-Debug-Zustand nicht klar. Betrachten wir jedoch die Funktion `die()`, die innerhalb von `do_trap` aufgerufen werden könnte. Der Code `void die(const char * str, struct pt_regs * regs, Der Code `long err) { CHK_REMOTE_DEBUG(1,SIGTRAP,err,regs,) do_exit(SIGSEGV); }` zeigt, dass die Ausnahmebehandlungsfunktion im Debug-Zustand weiterhin die Funktion `handle_exception` aufruft. Im Gegensatz zur oben genannten Ausnahmebehandlungsfunktion ist der Programmablauf jedoch im Debug- und Nicht-Debug-Zustand identisch. `handle_exception` liefert lediglich den aktuellen Systemzustand; die Fortsetzung der Ausführung führt weiterhin zu `do_exit`. Obwohl nicht alle Ausnahmebehandlungsfunktionen mit diesen beiden Methoden definiert sind, lassen sie sich im Wesentlichen in eine Kategorie einordnen. Die meisten Änderungen an den Behandlungsfunktionen betreffen den zweiten Typ, da der erste Ausnahmebehandlungstyp für das Debugging vorgesehen ist. Verfügt der Zielrechner über ein Debugging-Ausgabegerät, muss die zweite Ausnahmebehandlungsfunktion nicht geändert werden, da die Ausnahmebehandlungsfunktion des Linux-Kernels im Nicht-Debugging-Zustand bereits die notwendigen Status- und Fehlerinformationen ausgibt. 4.1.2 Steuermodul: Nachdem das Steuermodul die Kommunikation mit dem Host-GDB abgeschlossen hat, wird der spezifische Prozess ausgeführt. Abbildung 4 zeigt den Ablauf. Die Funktion `handle_exception` ermittelt zunächst, ob sich die CPU im VM86-Modus oder im Benutzermodus befindet. Ist dies der Fall, gibt sie einen Wert zurück, was bedeutet, dass KGDB nur Programme im Kernelmodus debuggt. Anschließend empfängt sie die von GDB gesendeten Informationen und führt basierend darauf entsprechende Operationen und Reaktionen aus. Der gestrichelte Kasten im Flussdiagramm zeigt den allgemeinen Ablauf der Funktion `handle_exception` in allen GDBstubs. 4.2 Anwendungs-Debugging-Modell In der Entwicklung eingebetteter Linux-Systeme wird häufig das Debugging-Agent-Tool GDBserver zum Debuggen von Anwendungen verwendet. Es kompiliert den Stub nicht in die zu debuggende Anwendung, sondern behandelt das zu debuggende Programm als Kindprozess von GDBserver. Dadurch kann GDBserver den vom Kernel bereitgestellten Code-Tracing-Mechanismus (ptrace) nutzen, um die Ausführung des zu debuggenden Prozesses zu überwachen und so den Debugging-Vorgang abzuschließen. Dieses Funktionsprinzip ähnelt dem lokalen Debugging von GDB. Das zugehörige Debugging-Modell ist in Abbildung 5 dargestellt. Der Workflow von GDBserver ist wie folgt: GDBserver erstellt einen Kindprozess. -> Bindet das Tracing-Tool ptrace(ptrace_traceme,,) -> Verschiedene Debugging-Befehle des Hosts werden von GDBserver für unterschiedliche Betriebsanforderungen in ptrace-Befehle umgewandelt. Die Verwendung von GDBserver für Remote-Debugging erfordert die Unterstützung des Kernel-Betriebssystems, einschließlich Kindprozessen und Code-Tracing-Mechanismen, was den Arbeitsaufwand für andere Embedded-System-Kernel erheblich erhöhen würde. Darüber hinaus hat ptrace seine Grenzen, z. B. kann es nur seine Kindprozesse verfolgen und nur ein einzelnes langes Datenwort zwischen dem debuggenden und dem zu debuggenden Prozess übertragen. Die Verwendung eines allgemeinen Debugging-Modus wäre weniger aufwendig. Wie in Abbildung 6 dargestellt, wird der Stub in die Anwendung kompiliert und ein Haltepunkt am Einstiegspunkt der Anwendung eingefügt. Die Steuerung wird zu Beginn des Programms an GDB übergeben, und der weitere Prozess ähnelt dem Debugging auf Kernel-Ebene. Abbildung 4. Flussdiagramm der handle_exception-Funktion in GDBKGDB 5. Debuggen der Anwendung mit GDBserver 6. Debuggen der Anwendung mit Stub 5. Das Debuggen der Anwendung ohne Kernelmodifikation mit GDB implementiert Haltepunkte durch Speicherlese-/Schreiboperationen, wobei die ursprüngliche Anweisung durch eine Trap-Anweisung ersetzt wird. Bei Ausführung dieser Anweisung tritt ein Einzelschritt-Debugging-Interrupt auf, und das Programm ruft die Ausnahmebehandlungsfunktion auf. Die verschiedenen Funktionen des Debuggers müssen entsprechende Operationen ausführen. Unterschiedliche Systeme bieten unterschiedliche Debugging-Ausnahmeanweisungen, wie z. B. int3, trap2 usw. Um die GDBstub-Debugging-Funktionalität zu implementieren, müssen die Ausnahmebehandlungsfunktionen dieser Anweisungen für die Verwendung der von den jeweiligen Hardwareplattformen bereitgestellten Haltepunktanweisungen neu geschrieben werden. Daher benötigen allgemeine Debugging-Systeme oder Debugging-Agenten Einzelschritt-Debugging-Anweisungsbehandlungsfunktionen, die vom Systemkernel unterstützt werden. KGDB, wie oben erwähnt, modifiziert die Ausnahmebehandlungsfunktion, und GDBserver benötigt die ptrace-Funktion des Systemkernels. Diese Methode hat einige Nachteile: Der Aufwand für die Kernelmodifikation ist hoch, und die Portabilität ist gering. Um diese Probleme zu beheben, kann ein anderes Haltepunkt-Implementierungsschema verwendet werden: die Definition einer Haltepunkt-Setzfunktion im Stub. Haltepunktfunktionen simulieren Debugging-Ausnahmen. Anweisungen zum Speichern des Kontexts, Aufrufen der Ausnahmebehandlungsfunktion, Wiederherstellen des Kontexts und Übergeben der Kontrolle an das zu debuggende Programm. Der grundlegende Ablauf einer Haltepunktfunktion ist wie folgt: #define BREAKPOINT __asm__ __volatile__(" bl ent_exception\n) void debug_trap() { __asm__ __volatile__( " ent_exception: \n" Kontext speichern" bl handle_exception \n" " out_exception: \n" Kontext wiederherstellen); } Der Ablauf der Funktion handle_exception() ähnelt dem gestrichelten Kasten in Abbildung 4. Ein wichtiger und für diese Methode entscheidender Punkt ist das Ersetzen der Haltepunktanweisungen. Der Binärcode der Haltepunkt-Ausnahmeanweisung, der von der Hardwareplattform bereitgestellt und beim Setzen des Haltepunkts von GDB übergeben wird, muss durch den neu definierten BREAKPOINT-Binärcode im Stub ersetzt werden, um die Ausnahmebehandlungsfunktion für das Debuggen aufzurufen. Daher ist in der Funktion handle_exception() bei der empfangenen Anfrage "M" eine Verarbeitung erforderlich, wie in Abbildung 7 dargestellt: Abbildung 7. Die Substitutionsmethode kann theoretisch sowohl beim Kernel- als auch beim Anwendungs-Debugging eingesetzt werden, ihre Vorteile sind jedoch beim Anwendungs-Debugging deutlicher. Diese Methode bindet den Kernel beim Schreiben des Stubs nicht ein und kann direkt im Benutzermodus ausgeführt werden, ohne dass beim Debuggen der Anwendung in den Kernelmodus gewechselt werden muss. Allerdings hat diese Methode auch Nachteile. Um den Kontext zu erhalten, muss der Benutzer die Systemregister verstehen. Mit zunehmender Komplexität des Stubs selbst ist eine verstärkte Überprüfung seiner Korrektheit erforderlich. 6. Fazit: Die Remote-Debugging-Methode mit Stubs ist komfortabel und effektiv und kann die Projektkosten senken, was zu ihrer weitverbreiteten Forschung und Anwendung in der Praxis geführt hat. Dieser Artikel beschreibt die erfolgreiche Anwendung der Methode zum Debuggen von Anwendungen ohne Kernelmodifikation auf unser selbstentwickeltes, auf einem Mikrokernel basierendes Betriebssystem. Sie bietet ein gutes Debugging-Werkzeug für die Entwicklung und Anwendung dieses Systems. Natürlich hat auch die Remote-Debugging-Methode mit Stubs einige Schwächen. Die Anwendung von Stubs basiert auf serieller Kommunikation; daher ist die Korrektheit der seriellen Portverarbeitung nicht immer gewährleistet. Die Kenntnis der Funktionen und der eigenen Verarbeitungsfunktionen des Stubs ist eine Voraussetzung für sicheres Stub-Debugging. Referenzen: [1] Li Hongwei, Li Cuiping, Analyse und Verbesserung des Linux-Kernel-Debuggings mit kgdb, Microcomputer and Application, Nr. 10, 2004. [2] Guo Shengchao, GDB Remote Debugging und seine Anwendung in eingebetteten Linux-Systemen, Computer Engineering and Application, Bd. 26, Nr. 10, 2004. [3] Peng Jinzhan, GRDBS: Ein allgemeines Remote-Debugging-System für eingebettete Systeme, Computer Engineering, Bd. 29, Nr. 2, Februar 2003. [4] Gatliff, Bill, Einbettung mit GNU: das gdb Remote Serial Protocol, Embedded Systems Programming, September 1999, S. 109. [5] Gilmore J, Shebs S, GDB Internals: Ein Leitfaden zu den Interna des GNU-Debuggers, Free Software Foundation Inc., 1999. Zugehörigkeit des Autors: Fachbereich Informatik, Zhejiang-Universität Adresse: Gebäude 4, Yuquan-Campus, Zhejiang-Universität, 230, 310027 E-Mail: [email protected]
Read next

Eine kurze Diskussion über die Präzisionssteuerung von SPSen in Druckmaschinen

1. Einleitung. In den letzten Jahren hat die Fabrikautomatisierung in China dank des Fortschritts der Automatisierungste...

Articles 2026-02-20