Alles im Blick
Watchpoints halten das Programm an, sobald sich eine bestimmte Variable ändert - auch dann, wenn die betreffende Zeile die Variable nicht explizit über ihren Namen anspricht. Stattdessen überwacht ein Watchpoint die Speicheradresse der Variablen und hält den Programmablauf an, wenn dort ein Wert geschrieben wird. Einen solchen Watchpoint setzt der Befehl »watch«:
(gdb) watch i Hardware
watchpoint 3: i
Sobald Sie das Programm fortsetzen, ist der Watchpoint aktiv:
(gdb) c
Continuing.
Hardware watchpoint 3: i
Old value = 1
New value = 23
0x080484db in CalcChangeFor (cent=23) at
change.c:16
16 for (i=cent;i>0;i--)
3: i = 23
Die Ausgabe zeigt, dass das Programm aus Listing 1 die Variable »i« in beiden »for«-Schleifen als Zähler verwendet. Das demonstriert neben der Arbeitsweise von Watchpoints den Sinn durchdachter Namenskonventionen.
Zu Testzwecken lässt sich der Wert einer Variablen beliebig verändern, um in den Programmablauf einzugreifen. Dazu dient der Befehl »set«:
(gdb) set var i = 0
Auch eine Funktion können Sie direkt mit Parametern aufrufen. Nimmt eine Funktion keine Parameter entgegen, verwenden Sie leere Klammern:
(gdb) call CalcChangeFor(20)
Haben Sie den Namen einer Schleifenvariablen geändert, verwenden Sie beispielsweise in der inneren »for«-Schleife als Zähler »j« statt »i«. Kompilieren Sie dann das Programm neu und beim nächsten Ausführen von »change« erhalten Sie als Ergebnis:
$ change 23
1 x 20c
1 x 2c
Allerdings stimmt dieses Ergebnis offensichtlich nicht. Ausprobieren ergibt, dass dieser Fehler bei jeder ungeraden Ausgangszahl auftritt. Eine neue Fehler- suchen-Runde beginnt. Wenn Sie einen Bug entdecken, hilft es generell weiter, die Fälle einzugrenzen, in denen er auftritt. Testen Sie die Grenzen des Programms, etwa mit negativen Zahlen, ungeraden Zahlen, geraden Zahlen, sehr großen Zahlen, sehr kleinen Zahlen. Diese Methode hilft dabei, den Codeabschnitt einzugrenzen, der für den Fehler verantwortlich sein könnte.
Da der Fehler in diesem Fall nur bei ungeraden Zahlen auftritt, liegt der Verdacht nahe, dass die Routine, die die einzelnen Cents behandelt, damit zu tun hat. Um genau diesen Fall zu untersuchen, setzen Sie einen bedingten Breakpoint, der das Programm erst dann anhält, wenn die Ein-Cent-Münzen an der Reihe sind:
(gdb) b 16 if val==1
Breakpoint 5 at 0x804855e: file change.c, line 16.
Da ein Breakpoint das Programm anhält, bevor es die Anweisung der betreffenden Zeile ausführt, setzen Sie den Breakpoint in der Zeile nach der Zuweisung der Variable »val«. Bedingte Breakpoints können sehr kompliziert werden, sodass hier Vorsicht geboten ist, damit sich in den angewandten Debugging-Techniken selbst keine Bugs einschleichen.
Exakt zählen
Das Programm hält beim erneuten Ausführen aber nicht an, der bedingte Breakpoint wird nicht ausgelöst. Die Variable »val« hat also nie den Wert 1 angenommen. Daraus lässt sich schließen, dass das Programm die Ein-Cent-Münzen ignoriert. Um der Ursache auf die Spur zu kommen, setzen Sie einen neuen Breakpoint auf die Zwei-Cent-Münzen:
(gdb) b 16 if val==2
Note: breakpoint 5 also set at pc 0x804855e.
Breakpoint 6 at 0x804855e: file change.c, line 16.
01 #include <stdio.h>
02 #include <stdlib.h>
03
04 void CalcChangeFor(int cent)
05 {
06 int i, j, val;
07 int coins[] = { 200,100,50,20,10,5,2,1,};
08
09 if (cent == 0)
10 return; /* ungültiger Betrag */
11 if (cent < 0)
12 return; /* genauer Betrag */
13
14 for(i=0;i<sizeof(coins)/sizeof(coins[0]);i++) { /* für jede Münze */
15 val = coins[i];
16 for(j=cent;j>0;j--)
17 if (val*j <= cent) {
18 printf("%d x %dcn", j, val);
19 CalcChangeFor(cent - j*val);
20 return;
21 }
22 }
23 }
24
25 void usage(void)
26 {
27 fprintf(stderr, "Eingabe: change <amount>n");
28 }
29
30 int main(int argc, char **argv)
31 {
32 if (argc > 1)
33 CalcChangeFor(atoi(argv[1]));
34 else
35 usage();
36 return 0;
37 }
|
|
Den Debugger sollten Sie nicht als korrigierende Maßnahme nach der Programmierung betrachten, sondern als selbstverständlichen Bestandteil des Entwicklungszyklus. Setzen Sie den Debugger bei neuem Code grundsätzlich ein. Arbeiten Sie sich zeilenweise vor und denken über jede Zeile nach. Wird jede Zeile ausgeführt? Werden die Fehlerbedingungen korrekt abgefangen? Werden die Schleifen korrekt beendet? Bedenken Sie dabei, dass auch Tastatureingaben ein Datenstrom sind, der ein EOF-Zeichen liefern kann.
Das sind nur einige der Fragen, die Sie sich und Ihrem Debugger stellen sollten, bevor Sie das Programm ausführen, um festzustellen, ob es schon läuft. Wenn Sie den Code schrittweise ausführen und dabei erkennen, dass er sich wie erwartet verhält, wird Ihr Programm wahrscheinlich schon beim ersten Versuch funktionieren.
|
Dieses Mal stoppt der Breakpoint das Programm und Sie können Schritt für Schritt beobachten, wie es mit den Zwei-Cent-Münzen umgeht. Danach verlässt es die Schleife, da es die siebte Iteration durchlaufen hat. Beim Nachzählen ergibt sich aber, dass das Array »coins« acht Elemente enthält. Fehler dieser Art entstehen häufig beim nachträglichen Einbau neuer Features. Listing 7 zeigt das korrigierte Programm.
Nach Abschluss des Debugging brauchen Sie das Programm nicht neu zu kompilieren, um die Debugging-Informationen daraus zu entfernen. Das erledigt das Programm »strip«:
strip -g change
Bei großen Programmen ist es mühsam, mit Hilfe von Breakpoints jede Schleife komplett zu durchlaufen. Deshalb gibt es die Binärer-Split-Technik. Damit grenzen Sie den Programmabschnitt ein, in dem ein Bug auftritt. Setzen Sie einen Breakpoint nach der ersten Hälfte des Code und führen das Programm aus. Läuft bis dahin alles nach Plan, liegt der Fehler wohl in der zweiten Hälfte. Halbieren Sie diese wiederum mit einem Breakpoint. So arbeiten Sie sich immer näher heran, bis Sie den fehlerhaften Bereich auf einen möglichst kleinen Abschnitt reduziert haben.
Natürlich funktioniert auch der binäre Split nicht in allen Fällen. Es kommt stets darauf an, die Tools und Techniken zu beherrschen und zu verstehen, um sie sinnvoll einzusetzen. (csc/ofr)
|
[1] GDB-Dokumentation: [http://www.gnu.org/software/gdb/documentation/]
[2] Splint: Herwert Kiram, "Vollwaschmittel": Linux-Magazin 06/03, S. 52
[3] Memory-Debugger: Herwert Kiram, "Gedächtnis-Training": Linux-Magazin 02/04, S. 102
[4] NPTL-Threads: Aleksandar Colovic, "Sauber eingefädelt": Linux-Magazin 01/05, S. 90
|
|
Steven Goodwin verbringt ein Drittel seiner Zeit damit, Code zu planen, das andere Drittel damit, Code zu schreiben, und den Rest damit, den Code zu debuggen.
|
| Whitepaper |
|
Usage Landscape Enterprise Open Source Data Integration
Die Nachfrage nach Datenintegrationslösungen für Unternehmen ist zunehmend gestiegen und vor allem das Interesse an Open Source Technologien wird immer größer. Doch wie und von wem werden Open Source Datenintegrationslösungen genutzt und welches Nutzungsverhalten lässt sich daraus ableiten? Das vorliegende White Paper präsentiert die Erfahrungswerte von über 1000 Open Source Nutzern und liefert fundierte Antworten auf diese Fragen.
Download PDF (Registrierung erforderlich)
|
|
Daten Migration - Eine Publikation von Bloor Research
Datenmigrationsprojekte überschreiten häufig das Budget, neigen zu Verzögerung und werden unter Umständen komplett abgebrochen. Bloor Research ist eines der weltweit führenden IT-Forschungs-, Analyse- und Beratungsunternehmen und wird in dem vorliegenden White Paper die wichtigsten Aspekte dieser Problematik näher beleuchten. Ferner werden praktische Empfehlungen für erfolgreiche Migrationsprojekte gegeben, die Sie auf Ihr nächstes Projekt übertragen können.
Download PDF (Registrierung erforderlich)
|
Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links"
nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedrucken Fassung entsprechen.
|