GDB Breakpoint Kommandos
Breakpoint command lists sind ein nützliches Feature des GDB und dennoch lese ich kaum von ihnen und sehe sie nur selten in Benutzung, selbst bei erfahrenen Anwendern. Daher möchte ich hier ein paar Nutzungsbeispiele geben.
Veröffentlicht am von Philipp Trommler. Dieser Beitrag wurde außerdem übersetzt nach: en.
Die Grundlagen sind ganz einfach: Nach man mindestens einen Breakpoint erstellt
hat, kann mit commands
eine Befehlsliste erstellt werden, die bei jedem
Treffer des Breakpoints abgearbeitet wird. Standardmäßig legt commands
diese
Liste für den zuletzt erstellten Breakpoint an, durch die Angabe einer Nummer
hinter commands
kann aber auch ein ein bestimmter Breakpoint ausgewählt
werden. Ist man einmal in der commands
-Umgebung, können dort alle Kommandos
aufgelistet werden, die auch im gewöhnlichen GDB-Prompt zur Verfügung stehen.
Verlassen wird die Umgebung durch die Eingabe von end
.
Innerhalb der commands
-Umgebung hat man Zugriff auf alle Variablen, die auch
im GDB-Prompt verfügbar wären, hätte der Breakpoint normal getroffen, also
globale Variablen, Funktionsargumente und lokale Variablen. Soll der aktuelle
Treffer des Breakpoints übersprungen werden, kann wie üblich continue
aufgerufen werden, bevor das abschließende end
erreicht wird. Dementsprechend
muss das finale end
erreicht werden, ohne ein continue
zu treffen, wenn man
in einen GDB-Prompt für den aktuellen Treffer des Breakpoints wechseln möchte.
Das selbe gilt analog auch für next
und step
.
Meiner Erfahrung nach kombiniert man commands
am besten mit silent
, wodurch
die typischen Ausgaben des GDB beim Erreichen eines Breakpoints unterbunden
werden. Insbesondere bei häufig getroffenen Breakpoints macht dies die Ausgaben
deutlich lesbarer. Außerdem kann man innerhalb der commands
-Umgebung die
selben Kurznamen für Befehle verwenden wie im Prompt, also zum Beispiel c
für
continue
. Um die Einstiegshürde niedrig zu halten habe ich darauf aber in
diesem Beitrag verzichtet.
Nun aber zu den Beispielen. Angenommen folgendes Problem: In einem langlaufenden
Programm kommt es von Zeit zu Zeit dazu, dass der Parameter einer Funktion nicht
wie erwartet ist und die Herkunft dieses ungewöhnlichen Wertes ist unklar. Zudem
kann das Problem nicht reproduziert werden. Eine solche Situation ist im unten
dargestellten Programm abstrahiert in der Funktion foo
dargestellt. Immer mal
wieder erhält diese Funktion statt eines gültigen Strings einen NULL
-Pointer.
Natürlich würden solche Fehler oft einfach einen SEGFAULT
hervorrufen und
benötigten daher gar keinen Breakpoint, aber nehmen wir einfach mal im Sinne des
Beispiels an, dem wäre nicht so.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Das Programm kann mit gcc -Wall -Wextra -Werror -std=c18 -g -o main main.c
kompiliert und sogar ausgeführt werden. Um das eigentliche Problem zu
untersuchen, starten wir es im GDB und geben folgendes ein:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Dies erzeugt einen Breakpoint für foo
und führt bei jedem Treffer dieses
Breakpoints die folgenden Befehle aus:
- Die üblichen GDB-Ausgaben beim Treffen eines Breakpoints werden unterbunden.
- Wenn das Argument
str
der Funktionfoo
einNULL
-Pointer ist, wird der aktuelle Backtrace ausgegeben und ein GDB-Prompt geöffnet. - Sonst wird das Programm weiter ausgeführt.
Und tatsächlich, nachdem run
aufgerufen worden ist, wird die Ausführung des
Programms an der Stelle unterbrochen, an der ein NULL
-Pointer in die Funktion
foo
übergeben wird:
1 2 3 4 5 |
|
Schlaue Köpfe mögen nun anmerken, dass ein nahezu gleiches Verhalten auch unter
Benutzung von bedingten Breakpoints à la break foo if 0x0 == str
hätte
erreicht werden können und das stimmt auch. Das Folgende kann jedoch nicht so
einfach repliziert werden: Angenommen, der Nutzer möchte bestimmte, eventuell
fehlerhafte, Werte zur Laufzeit in das Programm injizieren, um ein bestimmtes
Verhalten zu auszulösen, zum Beispiel während des Prototypings. Genau so, wie
innerhalb der commands
-Umgebung Werte abgefragt werden können, können diese
nämlich auch verändert werden.
Um dies zu verdeutlichen, wird die Funktion foo
aus dem vorigen Beispiel wie
folgt geändert, sodass sie die Werte, die ihr übergeben werden, ausgibt:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Das resultierende Programm kann noch immer wie zuvor kompiliert werden, beim
Versuch es auszuführen, kommt es jedoch zum SEGFAULT
, wenn der NULL
-Pointer
ausgegeben werden soll. Aber, wieder zum Wohle des Beispiels, angenommen, es
handele sich um ein weitaus komplexeres Programm, in dem zur Laufzeit Daten
geändert werden sollen, um ein bestimmtes Verhalten zu provozieren. Unter
Verwendung des set var
-Befehls des GDB in der commands
-Umgebung kann dieses
Ziel erreicht werden. Um beispielsweise bei jedem Aufruf von foo
den
str
-Parameter auf "bar"
zu setzten, können die folgenden Befehle genutzt
werden:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Und wie zu erwarten, wird bar
fünf Mal ausgegeben und es tritt kein SEGFAULT
mehr auf.
Natürlich sind dies nur sehr triviale Beispiele für den Einstieg in die GDB
Breakpoint Kommandos. Durch Verwendung sonstiger, aus der normalen Nutzung des
GDB bekannter Tricks können diese aber zu einem sehr nützlichen Instrument in
der Werkzeugkiste des Fehlersuchenden werden. Und tatsächlich: Seitdem ich die
commands
-Umgebung kenne, hat sich meine gesamte Nutzung des GDB dahingehend
verändert, dass ich sie regelmäßig einsetze.
Abgelegt unter Debugging. Tags: programming, gdb.
Willst du diesen Beitrag kommentieren? Schreib mir an blog [at] philipp-trommler [dot] me!