Der Debugger bei FreeBSD heißt
gdb
(GNU
debugger). Sie können Ihn durch die Eingabe
von
%
gdb progname
starten, wobei viele Leute ihn vorzugsweise innerhalb von Emacs aufrufen. Sie erreichen dies durch die Eingabe von:
M-x gdb RET progname
RET
Die Verwendung eines Debuggers erlaubt Ihnen Ihr Programm unter kontrollierteren Bedingungen ausführen zu können. Typischerweise können Sie so Zeile für Zeile durch Ihr Programm gehen, die Werte von Variablen untersuchen, diese verändern, dem Debugger sagen er soll das Programm bis zu einem bestimmten Punkt ausführen und dann anhalten, und so weiter und so fort. Sie können damit sogar ein schon laufendes Programm untersuchen, oder eine Datei mit einem Kernspeicherabbild laden um herauszufinden, warum das Programm abgestürzt ist. Es ist sogar möglich damit den Kernel zu debuggen, wobei dies etwas trickreicher als bei den Benutzeranwendungen ist, welche wir in diesem Abschnitt behandeln werden.
Der gdb
besitzt eine recht gute
Online-Hilfe, sowie einen Satz von Info-Seiten, weshalb sich
dieser Abschnitt auf ein paar grundlegende Befehle
beschränken wird.
Falls Sie den textbasierten Kommandozeilen-Stil
abstoßend finden gibt es ein graphisches Front-End
dafür (devel/xxgdb
) in der Ports-Sammlung.
Dieser Abschnitt ist als Einführung in die
Verwendung des gdb
gedacht und beinhaltet
nicht spezielle Themen wie das Debuggen des Kernels.
Sie müssen das Programm mit der Option
-g
kompiliert haben um den
gdb
effektiv einsetzen zu können. Es
geht auch ohne diese Option, allerdings werden Sie dann nur
den Namen der Funktion sehen, in der Sie sich gerade befinden,
anstatt direkt den zugehörigen Quelltext. Falls Sie eine
Meldung wie die folgende sehen:
wenn der gdb
gestartet wird, dann
wissen Sie, daß das Programm nicht mit der Option
-g
kompiliert wurde.
Geben Sie in der Eingabeaufforderung des
gdb
break main
ein.
Dies weist den Debugger an, dass Sie nicht daran interessiert sind,
den einleitenden Schritten beim Programmstart zuzusehen und dass
am Anfang Ihres Codes die Ausführung beginnen soll. Geben Sie
nun run
ein, um das Programm zu starten -
es wird starten und beim Aufruf von main()
vom
Debugger angehalten werden. (Falls Sie sich jemals gewundert haben von
welcher Stelle main()
aufgerufen wird, dann
wissen Sie es jetzt!).
Sie können nun Schritt für Schritt durch Ihr
Programm gehen, indem Sie n
drücken.
Wenn Sie zu einem Funktionsaufruf kommen können Sie diese
Funktion durch drücken von s
betreten.
Sobald Sie sich in einem Funktionsaufruf befinden können
Sie diesen durch drücken von f
wieder
verlassen. Sie können auch up
und
down
verwenden, um sich schnell den
Aufrufer einer Funktion anzusehen.
Hier ist ein einfaches Beispiel, wie man mit Hilfe des
gdb
einen Fehler in einem Programm findet.
Dies ist unser eigenes Programm (mit einem absichtlich
eingebauten Fehler):
Dieses Programm setzt i auf den Wert
5
und übergibt dies einer Funktion
bazz()
, welche den Wert ausgibt, den Sie
von uns erhalten hat.
Wenn wir das Programm kompilieren und ausführen erhalten wir
%
cc -g -o temp temp.c
%
./temp
This is my program
anint = 4231Das ist nicht was wir erwartet hatten! Es ist Zeit, daß wir sehen was hier passiert!
%
gdb temp
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) break main
Skip the set-up code
Breakpoint 1 at 0x160f: file temp.c, line 9. gdb
puts breakpoint at main()
(gdb) run
Run as far as main()
Starting program: /home/james/tmp/temp Program starts running
Breakpoint 1, main () at temp.c:9 gdb
stops at main()
(gdb) n
Go to next line
This is my program Program prints out
(gdb) s
step into bazz()
bazz (anint=4231) at temp.c:17 gdb
displays stack frame
(gdb)Halt mal! Wieso hat denn anint den Wert
4231
? Haben wir dieser Variablen nicht in
main()
den Wert 5
zugewiesen? Gehen wir mal zurück zu
main()
und schauen dort nach.
up
Move up call stack
#1 0x1625 in main () at temp.c:11 gdb
displays stack frame
(gdb) p i
Show us the value of i
$1 = 4231 gdb
displays 4231
Oh! Anscheinend haben wir vergessen i zu initialisieren. Wir wollten eigentlich
schreiben, haben aber die Zeile mit
i=5;
vergessen. Da wir i
nicht initialisiert haben hatte diese Variable gerade den
Wert, der in dem ihr zugewiesenen Speicherbereich stand als
wir das Programm gestartet haben, welcher in diesem Fall
4231
war.
Der gdb
zeigt jedes mal, wenn wir
eine Funktion betreten oder verlassen, den Inhalt des
Stack-Rahmens an, selbst wenn wir uns mit
up
und down
im
Aufruf-Stack umher bewegen. Dabei wird der Name der Funktion
sowie der übergebenen Argumente angezeigt, was uns
dabei hilft, die Übersicht zu behalten. (Der Stack ist
ein Speicherbereich, in dem ein Programm Informationen
über die an eine Funktion übergebenen Argumente
ablegt, sowie die Rücksprungadresse eines
Funktionsaufrufes).
Eine Kernspeicherdatei ist im Prinzip eine Datei, die den
vollständigen Zustand eines Prozesses enthält, als
dieses abgestürzt ist. In „den guten alten
Zeiten“ mußten Programmierer hexadezimale Listen
der Kernspeicherdatei ausdrucken und über
Maschinencodehandbüchern schwitzen, aber heutzutage ist
das Leben etwas einfacher geworden. Zufälligerweise wird
die Kernspeicherdatei unter FreeBSD und anderen
4.4BSD-Systemen
anstatt einfach nur progname
.corecore
genannt, um
deutlich zu machen, zu welchem Programm eine Kernspeicherdatei
gehört.
Um eine Kernspeicherdatei zu untersuchen müssen Sie
den gdb
wie gewohnt starten. An Stelle von
break
oder run
müssen Sie das Folgende eingeben
core progname
.core
Wenn Sie sich nicht in demselben Verzeichnis befinden wie
die Kernspeicherdatei müssen Sie zuerst dir
/path/to/core/file
eingeben.
Sie sollten dann etwas wie folgt sehen:
%
gdb a.out
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc.
(gdb) core a.out.core
Core was generated by `a.out'.
Program terminated with signal 11, Segmentation fault.
Cannot access memory at address 0x7020796d.
#0 0x164a in bazz (anint=0x5) at temp.c:17
(gdb)In diesem Fall hieß das Programm
a.out
, weshalb die Kernspeicherdatei den
Namen a.out.core
trägt. Wie wir
sehen können stürzte das Programm in einer Funktion
namens bazz
ab, als es versuchte auf
einen Speicherbereich zuzugreifen, der dem Programm nicht zur
Verfügung stand.
Manchmal ist es ganz nützlich zu sehen, wie eine
Funktion aufgerufen wurde, da bei komplexen Programmen das
eigentliche Problem schon sehr viel weiter oben auf dem
Aufruf-Stack aufgetreten sein könnte. Der Befehl
bt
veranlaßt den
gdb
dazu, einen Backtrace des Aufruf-Stacks
auszugeben:
bt
#0 0x164a in bazz (anint=0x5) at temp.c:17
#1 0xefbfd888 in end ()
#2 0x162c in main () at temp.c:11
(gdb)Die Funktion end()
wird aufgerufen,
wenn ein Programm abstürzt; in diesem Fall wurde die
Funktion bazz()
aus der
main()
-Funktion heraus aufgerufen.
Eine der tollsten Features des gdb
ist die Möglichkeit, damit bereits laufende Programme zu
untersuchen. Dies bedeutet natürlich, daß Sie die
erforderlichen Rechte dafür besitzen. Ein häufig
auftretendes Problem ist das Untersuchen eines Programmes,
welches sich selber forkt. Vielleicht will man den Kindprozess
untersuchen, aber der Debugger erlaubt einem nur den Zugriff
auf den Elternprozess.
Was Sie an solch einer Stelle machen ist, Sie starten
einen weiteren gdb
, ermitteln mit Hilfe von
ps
die Prozess-ID des Kindprozesses, und
geben
attach pid
im gdb
ein, und können dann wie
üblich mit der Fehlersuche fortfahren.
„Das ist zwar alles sehr schön,“ werden
Sie jetzt vielleicht denken, „aber in der Zeit, in der
ich diese Schritte durchführe, ist der Kindprozess schon
längst über alle Berge“. Fürchtet euch
nicht, edler Leser, denn Ihr müßt wie folgt
vorgehen (freundlicherweise zur Verfügung gestellt von
den Info-Seite des gdb
):
Alles was Sie jetzt noch tun müssen ist, sich an
den Kindprozess ranzuhängen, PauseMode
auf 0
zu setzen und auf den
sleep()
Funktionsaufruf zu warten, um
zurückzukehren!
Wenn Sie Fragen zu FreeBSD haben, schicken Sie eine E-Mail an
<de-bsd-questions@de.FreeBSD.org>.
Wenn Sie Fragen zu dieser Dokumentation haben, schicken Sie eine E-Mail an
<de-bsd-translators@de.FreeBSD.org>.