FreeBSD Developers' Handbook
Zurück | Kapitel 2. Werkzeuge zur Programmierung | Weiter

# 2.6. Debuggen

## 2.6.1. Der Debugger

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.

## 2.6.2. Ein Programm im Debugger ausführen

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:

    … (no debugging symbols found) …

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):

    #include <stdio.h>

    int bazz(int anint);

    main() {
    	int i;

    	printf("This is my program\n");
    	bazz(i);
    	return 0;
    }

    int bazz(int anint) {
    	printf("You gave me %d\n", anint);
    	return anint;
    }

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 = 4231

Das 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.

    (gdb) 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

    …
    main() {
    	int i;

    	i = 5;
    	printf("This is my program\n");
    …

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.

> Anmerkung: 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).

## 2.6.3. Eine Kernspeicherdatei untersuchen

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 ``progname`.core` anstatt einfach nur `core` 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

    (gdb) 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:

    (gdb) 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.

## 2.6.4. Ein bereits laufendes Programm untersuchen

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

    (gdb) 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`):

    …
    if ((pid = fork()) < 0)		/* _Always_ check this */
    	error();
    else if (pid == 0) {		/* child */
    	int PauseMode = 1;

    	while (PauseMode)
    		sleep(10);	/* Wait until someone attaches to us */
    	…
    } else {			/* parent */
    	…

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!

Zurück | Zum Anfang | Weiter
Make | Nach oben | Emacs als Entwicklungsumgebung verwenden

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>.