Open Source im professionellen Einsatz

Newsletter abonnieren
Seite durchsuchen

HEFTARCHIV | NEWS | E-BIBLIOTHEK | VIDEO | BLOGS | WHITEPAPER | EVENTS | ACADEMY | ABO | SHOP

user friendly

  Home  »  Heft & Abo  »  Heftarchiv  »  2005  »  04  »  Auf der Pirsch  

RSS-Feed der aktuellen News von Linux-Magazin Online Folgen Sie Linux-Magazin Online auf Twitter
Diesen Artikel druckenDiesen Artikel weiterempfehlen Diesen Artikel kommentieren Newsletter abonnieren
Share/Bookmark

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.

Listing 7: Korrigierte Version
von »change«

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  }

Auf Tuchfühlung

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)

Infos

[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

Der Autor

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.

Diesen Artikel druckenDiesen Artikel weiterempfehlen Diesen Artikel kommentieren Newsletter abonnieren
Share/Bookmark
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)
Kommentare (0)