Der GNU Debugger (gdb) ist ein unschätzbares Werkzeug zum Untersuchen laufender Prozesse und zum Beheben von Problemen, während Sie Programme entwickeln.
Sie können Breakpoints an bestimmten Stellen setzen (nach Funktionsname, Zeilennummer usw.), diese Breakpoints aktivieren und deaktivieren, Variablenwerte anzeigen und ändern und alle Standardaufgaben ausführen, die Sie von einem Debugger erwarten würden. Aber es hat viele andere Funktionen, mit denen Sie vielleicht nicht experimentiert haben. Hier sind fünf, die Sie ausprobieren können.
Bedingte Haltepunkte
Das Setzen eines Breakpoints ist eines der ersten Dinge, die Sie mit dem GNU-Debugger lernen werden. Das Programm stoppt, wenn es einen Haltepunkt erreicht, und Sie können gdb-Befehle ausführen, um es zu untersuchen oder Variablen zu ändern, bevor Sie dem Programm erlauben, fortzufahren.
Sie wissen vielleicht, dass eine oft aufgerufene Funktion manchmal abstürzt, aber nur, wenn sie einen bestimmten Parameterwert erhält. Sie könnten am Anfang dieser Funktion einen Haltepunkt setzen und das Programm ausführen. Die Funktionsparameter werden jedes Mal angezeigt, wenn der Haltepunkt erreicht wird, und wenn der Parameterwert, der den Absturz auslöst, nicht angegeben wird, können Sie fortfahren, bis die Funktion erneut aufgerufen wird. Wenn der problematische Parameter einen Absturz auslöst, können Sie den Code schrittweise durchgehen, um zu sehen, was falsch ist.
(gdb) break sometimes_crashes
Breakpoint 1 at 0x40110e: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Um dies wiederholbarer zu machen, könnten Sie zählen, wie oft die Funktion vor dem bestimmten Aufruf aufgerufen wird, an dem Sie interessiert sind, und einen Zähler auf diesen Haltepunkt setzen (z der Haltepunkt).
Aber wo Breakpoints wirklich leistungsfähig werden, ist ihre Fähigkeit, Ausdrücke zur Laufzeit auszuwerten, wodurch Sie diese Art von Tests automatisieren können. Geben Sie ein:bedingte Haltepunkte.
break [LOCATION] if CONDITION
(gdb) break sometimes_crashes if !f
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
(gdb)
Anstatt gdb jedes Mal fragen zu lassen, was zu tun ist, wenn die Funktion aufgerufen wird, ermöglicht Ihnen ein bedingter Haltepunkt, dass gdb nur dann an dieser Stelle stoppt, wenn ein bestimmter Ausdruck als wahr ausgewertet wird. Wenn die Ausführung die Position des bedingten Haltepunkts erreicht, der Ausdruck jedoch als falsch ausgewertet wird, wird die
Weitere Linux-Ressourcen
- Spickzettel für Linux-Befehle
- Spickzettel für fortgeschrittene Linux-Befehle
- Kostenloser Online-Kurs:RHEL Technical Overview
- Spickzettel für Linux-Netzwerke
- SELinux-Spickzettel
- Spickzettel für allgemeine Linux-Befehle
- Was sind Linux-Container?
- Unsere neuesten Linux-Artikel
Der Debugger lässt das Programm automatisch fortfahren, ohne den Benutzer zu fragen, was er tun soll.
Breakpoint-Befehle
Ein noch ausgeklügelteres Merkmal von Breakpoints im GNU Debugger ist die Möglichkeit, eine Antwort auf das Erreichen eines Breakpoints per Skript zu schreiben. Breakpoint-Befehle ermöglichen es Ihnen, eine Liste von GNU Debugger-Befehlen zu schreiben, die ausgeführt werden, wenn ein Breakpoint erreicht wird.
Wir können dies verwenden, um den Fehler zu umgehen, den wir bereits in den sometimes_crashes kennen Funktion und sorgen dafür, dass sie harmlos von dieser Funktion zurückkehrt, wenn sie einen Nullzeiger bereitstellt.
Wir können leise verwenden als erste Zeile, um mehr Kontrolle über die Ausgabe zu erhalten. Ohne dies wird der Stapelrahmen jedes Mal angezeigt, wenn der Haltepunkt erreicht wird, sogar bevor unsere Haltepunktbefehle ausgeführt werden.
(gdb) break sometimes_crashes
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if !f
>frame
>printf "Skipping call\n"
>return 0
>continue
>end
>printf "Continuing\n"
>continue
>end
(gdb) run
Starting program: /home/twaugh/Documents/GDB/prog
warning: Loadable section ".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0 sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
Skipping call
[Inferior 1 (process 9373) exited normally]
(gdb)
Binären Speicher ausgeben
GNU Debugger hat eine eingebaute Unterstützung für die Untersuchung des Speichers mit dem x Befehl in verschiedenen Formaten, einschließlich oktal, hexadezimal usw. Aber ich sehe gerne zwei Formate nebeneinander:hexadezimale Bytes auf der linken Seite und ASCII-Zeichen, die durch dieselben Bytes auf der rechten Seite dargestellt werden.
Wenn ich den Inhalt einer Datei Byte für Byte anzeigen möchte, verwende ich häufig hexdump -C (Hexdump stammt aus dem Paket util-linux). Hier ist gdbs x Befehl, der hexadezimale Bytes anzeigt:
(gdb) x/33xb mydata
0x404040 <mydata>: 0x02 0x01 0x00 0x02 0x00 0x00 0x00 0x01
0x404048 <mydata+8>: 0x01 0x47 0x00 0x12 0x61 0x74 0x74 0x72
0x404050 <mydata+16>: 0x69 0x62 0x75 0x74 0x65 0x73 0x2d 0x63
0x404058 <mydata+24>: 0x68 0x61 0x72 0x73 0x65 0x75 0x00 0x05
0x404060 <mydata+32>: 0x00
Was wäre, wenn Sie gdb beibringen könnten, Speicher genau wie hexdump anzuzeigen? Sie können, und tatsächlich können Sie diese Methode für jedes Format verwenden, das Sie bevorzugen.
Durch Kombinieren der dump Befehl zum Speichern der Bytes in einer Datei, der Shell Befehl zum Ausführen von hexdump für die Datei und die define Befehl können wir unseren eigenen neuen Hexdump erstellen Befehl zur Verwendung von Hexdump zur Anzeige des Speicherinhalts.
(gdb) define hexdump
Type commands for definition of "hexdump".
End with a line saying just "end".
>dump binary memory /tmp/dump.bin $arg0 $arg0+$arg1
>shell hexdump -C /tmp/dump.bin
>end
Diese Befehle können sogar in ~/.gdbinit aufgenommen werden Datei, um den Hexdump-Befehl dauerhaft zu definieren. Hier ist es in Aktion:
(gdb) hexdump mydata sizeof(mydata)
00000000 02 01 00 02 00 00 00 01 01 47 00 12 61 74 74 72 |.........G..attr|
00000010 69 62 75 74 65 73 2d 63 68 61 72 73 65 75 00 05 |ibutes-charseu..|
00000020 00 |.|
00000021
Inline-Disassemblierung
Manchmal möchten Sie mehr darüber erfahren, was zu einem Absturz geführt hat, und der Quellcode reicht nicht aus. Sie möchten sehen, was auf der Ebene der CPU-Anweisungen vor sich geht.
Die Zerlegung Mit dem Befehl können Sie die CPU-Anweisungen sehen, die eine Funktion implementieren. Aber manchmal kann die Ausgabe schwer zu verfolgen sein. Normalerweise möchte ich sehen, welche Anweisungen einem bestimmten Abschnitt des Quellcodes in der Funktion entsprechen. Verwenden Sie dazu /s Modifikator, um Quellcodezeilen in die Disassemblierung aufzunehmen.
(gdb) disassemble/s main
Dump of assembler code for function main:
prog.c:
11 {
0x0000000000401158 <+0>: push %rbp
0x0000000000401159 <+1>: mov %rsp,%rbp
0x000000000040115c <+4>: sub $0x10,%rsp
12 int n = 0;
0x0000000000401160 <+8>: movl $0x0,-0x4(%rbp)
13 sometimes_crashes(&n);
0x0000000000401167 <+15>: lea -0x4(%rbp),%rax
0x000000000040116b <+19>: mov %rax,%rdi
0x000000000040116e <+22>: callq 0x401126 <sometimes_crashes>
[...snipped...]
Dies wird zusammen mit info registriert um die aktuellen Werte aller CPU-Register und Befehle wie stepi zu sehen eine Anweisung nach der anderen auszuführen, ermöglichen Ihnen ein viel detaillierteres Verständnis des Programms.
Umgekehrtes Debuggen
Manchmal wünscht man sich, man könnte die Zeit zurückdrehen. Stellen Sie sich vor, Sie haben einen Überwachungspunkt für eine Variable erreicht. Ein Watchpoint ist wie ein Haltepunkt, aber anstatt an einer Stelle im Programm gesetzt zu werden, wird er auf einen Ausdruck gesetzt (unter Verwendung der watch Befehl). Immer wenn sich der Wert des Ausdrucks ändert, stoppt die Ausführung und der Debugger übernimmt die Kontrolle.
Stellen Sie sich also vor, Sie haben diesen Überwachungspunkt erreicht und der von einer Variablen verwendete Speicher hat seinen Wert geändert. Dies kann durch etwas verursacht werden, das viel früher aufgetreten ist; Beispielsweise wurde der Speicher freigegeben und wird nun wiederverwendet. Aber wann und warum wurde es befreit?
Der GNU Debugger kann sogar dieses Problem lösen, weil Sie Ihr Programm rückwärts ausführen können!
Dies wird erreicht, indem der Zustand des Programms bei jedem Schritt sorgfältig aufgezeichnet wird, sodass zuvor aufgezeichnete Zustände wiederhergestellt werden können, wodurch die Illusion entsteht, dass die Zeit rückwärts fließt.
Um diese Statusaufzeichnung zu aktivieren, verwenden Sie das Ziel record-full Befehl. Dann können Sie unmöglich klingende Befehle verwenden, wie zum Beispiel:
- Rückwärtsschritt , das zur vorherigen Quellzeile zurückspult
- Rückwärts-Weiter , das zur vorherigen Quellzeile zurückspringt und Funktionsaufrufe rückwärts durchläuft
- umgekehrtes Finish , das zu dem Punkt zurückspult, an dem die aktuelle Funktion aufgerufen werden sollte
- Rückwärts-Weiter , das zum vorherigen Zustand im Programm zurückspringt, der (jetzt) einen Haltepunkt (oder irgendetwas anderes, das zum Stoppen führt) auslösen würde
Hier ist ein Beispiel für Reverse-Debugging in Aktion:
(gdb) b main
Breakpoint 1 at 0x401160: file prog.c, line 12.
(gdb) r
Starting program: /home/twaugh/Documents/GDB/prog
[...]
Breakpoint 1, main () at prog.c:12
12 int n = 0;
(gdb) target record-full
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:7
7 return *f;
(gdb) reverse-finish
Run back to call of #0 0x0000000000401154 in sometimes_crashes (f=0x0)
at prog.c:7
0x0000000000401190 in main () at prog.c:16
16 sometimes_crashes(0);
Dies sind nur einige nützliche Dinge, die der GNU Debugger tun kann. Es gibt noch viele weitere zu entdecken. Welches versteckte, wenig bekannte oder einfach nur erstaunliche Feature von gdb ist Ihr Favorit? Bitte teilen Sie es in den Kommentaren.