In diesem Blogpost zeige ich, wie man mit gdb nutzen kann, um eine Variable in einem zu ändern.

minimales C-Programm

Der einfachste Fall ist folgendes C-Programm, welches in Sekundentakt einen Integer-Wert ausgibt:

$ cat > main.c <<EOF
#include <stdio.h>
#include <unistd.h>

int main() {
    int my_integer = 1337;

    while (1) {
        printf("%d\n", my_integer);
        sleep(1);
}
EOF

Das Programm wird mit Debug-Symbolen kompiliert:

$ gcc -g main.c

Anschließend kann man seine funktion testen:

$ ./a.out
1337
1337
1337

Attach mit gdb

Während das Programm läuft, hängt man sich jetzt mit gdb ran. Dabei wird das Programm an der Stelle "pausiert", an dem es gerade war. Wir haben hier nur 2 Funktions-Aufrufe, printf und sleep. Wenn man extrem viel Glück hat, weiß der Debugger jetzt schon, wo im Programm man sich befindet. Da das Programm aber die meiste Zeit in der sleep Funktion hängt, ist das unwahrscheinlich, es sei denn man nutzt eine libc, die ebenfalls mit Debug-Symbolen kompiliert wurde. Normalerweise ist das nicht der Fall. Daher kennt der Debugger auch noch nicht die Stelle, an der man sich im Programm befindet. Um das zu erreichen müssen wir einige Instruktionen ausführen; eben bis die Funktionsgrenzen bekannt sind. Mit dem Befehl ni (= next instruction) können wir selbiges auslösen:

For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 32505
Reading symbols from /root/tmp/c-test/a.out...
Reading symbols from /lib/ld-musl-x86_64.so.1...
(No debugging symbols found in /lib/ld-musl-x86_64.so.1)
0x00007f018f96aced in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f96aced in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968125 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968126 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968127 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f96812b in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968152 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968153 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968154 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f968155 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f96c2fc in nanosleep () from /lib/ld-musl-x86_64.so.1
(gdb)

Ich nutze hier ein System mit musl statt glibc als Basis. Wir sehen, dass der Code die ganze Zeit aus irgendeiner Funktion innerhalb des shared objects ld-musl-x86 kommt, können den Code aber noch nicht zuordnen. Erst in der letzten Zeile weiß der Debugger, dass die Instruktionen zur Funktion nanosleep gehörten. Auch wenn für die Bibliothek keine Debug-Symbole zur Verfügung stehen können wir jetzt mit einem step weitermachen, das heißt alle Instruktionen der nächsten Funktion ausführen:

0x00007f018f968155 in ?? () from /lib/ld-musl-x86_64.so.1
(gdb) ni
0x00007f018f96c2fc in nanosleep () from /lib/ld-musl-x86_64.so.1
(gdb) step
Single stepping until exit from function nanosleep,
which has no line number information.
main () at main.c:8
8                       printf("%d\n", my_integer);
(gdb)

Jetzt weiß der Debugger auch, an welcher Stelle im eigentlichen Programm wir uns befinden: Nämlich bei printf. Das war im Prinzip auch schon alles. Wir haben jetzt alle Möglichkeiten, die der Debugger bietet, um Änderungen vorzunehmen, den Stack zu inspizieren, nach bestimmten Byte-Mustern zu suchen oder was auch immer.

Zum Ändern einer Variable schauen wir uns erst die locals an, überschreiben dann die gewünschte Variable, und prüfen nochmal gegen:

(gdb) info locals
my_integer = 1337
(gdb) set variable my_integer = 9999
(gdb) info locals
my_integer = 9999
(gdb)

Fortsetzen lässt sich das Programm jetzt auf zweierlei Weise: Entweder durch beenden des Debuggers (quit), oder innerhalb des Debuggers mit c (= continue). Letzteres hat den Vorteil, dass man die Ausführung wieder unterbrechen kann. Lassen wir also das Programm einige Sekunden laufen, und halten es mit SIGINT (STRG+c) wieder an:

(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00007f018f96aced in ?? () from /lib/ld-musl-x86_64.so.1
(gdb)

Im Ausgabefenster unseres Programms sollte jetzt ein paar mal die neue Zahl aufgetaucht sein.

Nächster Beitrag Vorheriger Beitrag