Home » Tutorials » Programmierkonzepte » Fehlerbehandlung

Fehlerbehandlung

Die Elemente des Debuggers

Um Programme mit dem Debugger zu inspizieren, müssen wir natürlich seine Funktionalität kennen. Wir gehen dabei von folgendem, korrekten Konsolenprogramm aus:

program Primzahl;
{$APPTYPE CONSOLE}
uses
  SysUtils;

const
  MAX = 100; //Alle Primzahlen bis 100

function IstPrimzahl(APruefzahl: Integer): Boolean;
var
  teiler: Integer;
begin
  Result := True; //Zahl ist Primzahl (Annahme)
  teiler := 2;
  while Result and (Sqr(teiler) <= APruefzahl) do
  begin
    if APruefzahl mod teiler  0 then //Nicht ganzzahlig teilbar?
      Inc(teiler)
    else
      Result := False; //Keine Primzahl
  end;
end;

var
  i: Integer;
begin
  try
    for i := 2 to MAX do
      if IstPrimzahl(i) then
        WriteLn(i);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

Dieses Konsolenprogramm speichern wir und erzeugen das Projekt mit Debug-Informationen. Starten wir nun das Programm mit Debug-Unterstützung durch F9, über den Menüpunkt Start/Start oder mit dem entsprechenden Symbol, dann ersehen wir die Funktion des Programms – es gibt alle Primzahlen <= 100 aus. Darunterliegend erkennen wir das Debug-Layout der Oberfläche, welches außerdem in der Desktop-Symbolleiste ausgewählt werden kann.

Haltepunkte

Um zu einem bestimmten Zeitpunkt nähere Informationen zum Programm zu erhalten, müssen wir den Programmlauf anhalten. Das erreichen wir durch Setzen eines Haltepunktes im Quelltext. Mit dem Kompilieren/Erzeugen des Programms erscheinen am linken Rand des Editors die möglichen Kandidaten für solch einen Quelltexthaltepunkt, gekennzeichnet durch einen blauen Punkt. Klicken wir den Punkt neben der For-Schleife in Zeile 28 an, so sehen wir die farblich hinterlegten Merkmale eines aktivierten Haltepunktes.

bild4.png

Bei Starten des Programms mit F9 stoppt nun die Ausführung immer vor Abarbeitung dieser Anweisung in Zeile 28.
Der blaue Pfeil und die Hinterlegung im Quelltext kennzeichnen die nächste auszuführende Zeile. Wir können solche Haltepunkte aber auch mit Bedingungen versehen. Die Programmausführung stoppt dann also nicht immer, sondern nur, wenn die gesetzte Bedingung erfüllt ist. Dazu tätigen wir einen Rechtsklick auf den bereits gesetzten Haltepunkt und wählen „Eigenschaften des Haltepunkts…“ aus. Als Bedingung können wir hier boolesche Ausdrücke wie z.B. „i mod 10 = 0“ oder „i >= 30“ eintragen. Im ersten Fall ergibt die Bedingung True, wenn i die Werte 10, 20, …, 100 annimmt und im zweiten Fall, bei Werten von 30, 31, …, 100.
Eine weitere auswählbare Bedingung wäre in diesem Fenster der Durchlaufzähler. Tragen wir dort z.B. eine „10“ ein, dann stoppt die Anwendung, bevor die Haltepunktzeile zum 10. Mal abgearbeitet wird. Da die Schleife bei 2 beginnt, wäre zu diesem Zeitpunkt i=11. Danach wird der Durchlaufzähler wieder auf 0 gesetzt und das Zählen der Durchläufe beginnt von vorn.
Die Verwaltung der Haltepunkte ist im unteren Bereich des Debug-Layouts zu finden. Dort, wie auch am Haltepunkt selbst, können die gesetzten Eigenschaften eingesehen und verändert werden.
Neben den Quelltexthaltepunkten haben wir zusätzlich die Möglichkeit Adresshaltepunkte zu setzen. Hier hält die Ausführung an, wenn eine zuvor angegebene Speicheradresse im Arbeitsspeicher angesprochen wird. Das Setzen eines Adresshaltepunktes ist nur zur Laufzeit möglich und erreichbar über den Menüpunkt Start/Haltepunkt hinzufügen/Adresshaltepunkt. Interessant sind solche Adresshaltepunkte dann, wenn wir Fehlermeldungen erhalten, wo konstant auf eine bestimmte Speicheradresse zugegriffen werden soll.

Durchschreiten des Quelltextes

Die Haltepunkte allein nutzen relativ wenig. Wir müssen uns natürlich auch, nachdem wir die Ausführung angehalten haben, durch den Quelltext bewegen können. Steuern können wir das im Menüpunkt Start oder durch die Tasten F4, F7, F8 und F9.
F9 zeigt das bekannte Verhalten, die Anwendung läuft durch bzw. hält bei anfallenden Eingaben oder Haltepunkten. Mit F4 läuft das Programm bis zur aktuellen Cursor-Position, wiederum unter Berücksichtigung der angesprochenen Einschränkungen. Für uns interessanter ist das Verhalten bei F7 und F8. Mit F8 wird nur die nächste auszuführende Zeile komplett abgearbeitet, bei F7 wird dabei in eine etwaige Routine verzweigt, sofern der Quelltext davon vorliegt.
Wir können das unterschiedliche Verhalten, mit dem Haltepunkt in Zeile 28 beleuchten. Mit dem Starten des Programms durch F9 gelangen wir zum Haltepunkt. Mit F9 können wir nun den gesamten Schleifendurchgang, das Prüfen und Schreiben, auf einmal erledigen und erreichen so wieder direkt den Haltepunkt. F8 steuert jede Zeile einzeln an und F7 verzweigt gar in die Funktion IstPrimzahl hinein. Da wir hier schrittweise arbeiten, müssen wir auch jeden Schritt, per Tastendruck, auslösen. Die Abarbeitung endet mit Ende des Programms oder dem Drücken von Strg+F2 bzw. dem Klick auf das Icon von „Programm abbrechen“ in der Symbolleiste zur Fehlersuche.

Überwachte Ausdrücke

Nachdem wir nun wissen, wie die Programmausführung gezielt angehalten und auch wieder fortgesetzt werden kann, widmen wir uns dem Inspizieren der Daten. Wir können durch den Menüpunkt Start/Ausdruck hinzufügen oder durch Strg+F5 der Liste überwachter Ausdrücke einen weiteren hinzufügen – je nach Cursor-Position und Markierungen im Quelltext auch ohne weitere Eingabe und Bestätigung. Die Verwaltung dieser Ausdrücke findet im linken Bereich des Debug-Layouts statt.
Mit unserem Haltepunkt in Zeile 28 starten wir nun das Programm durch F9. Mittels Strg+F5, Eingabe von i als Ausdruck und Bestätigung, fügen wir die Variable i den überwachten Ausdrücken hinzu.

bild5.png

Zudem markieren wir den Ausdruck „APruefzahl mod teiler“ aus der Funktion IstPrimzahl in Zeile 17 und übernehmen ihn durch Strg+F5 direkt in die Liste. In der Liste der überwachten Ausdrücke sehen wir nun die beiden Ausdrücke und erkennen auch direkt eine Problemstellung – den Gültigkeitsbereich. Da wir uns momentan im Hauptprogramm befinden, kann die Restwertdivision, mit ihren lokalen Variablen, nicht ausgewertet werden. Erst wenn wir mit F7 in die Funktion hineinspringen, werden die lokalen Variablen gültig und dadurch der Ausdruck auswertbar.
Wir können außerdem Funktionsergebnisse auswerten und darstellen lassen. Dazu markieren wir den Aufruf „IstPrimzahl(i)“ in Zeile 29 und ergänzen die Liste überwachter Ausdrücke durch Strg-F5. Gehen wir jetzt schrittweise durch den Text, dann ist der Wert jedoch nicht verfügbar. Mit einem Rechtsklick auf den entsprechenden Listeneintrag und der Auswahl von Ausdruck bearbeiten bzw. durch Strg-E bei selektiertem Listeneintrag, erhalten wir seine Eigenschaften. Dort ändern wir die Standardeinstellungen und aktivieren „Seiteneffekte und Funktionsaufrufe zulassen“. Ab jetzt wird die Funktion, bei jedem Schritt im Quelltext ausgewertet und dargestellt, sofern dies möglich ist.
Über den Menüpunkt Start/Auswerten/Ändern bzw. durch Strg+F7 ist es uns zusätzlich möglich, den Wert von Ausdrücken zu ändern und somit deren Ergebnis zu manipulieren.

bild6.png

Die Aufnahme eines Ausdrucks geschieht identisch zu Strg+F5. Die Schaltfläche Auswerten erzeugt dabei im Anzeigefeld Ergebnis den aktuellen Rückgabewert, welchen wir im darunterliegenden Feld editieren können. Durch die Schaltfläche Ändern wird der Wert aktualisiert und hat damit Bestand bis zur nächsten Auswertung des Ausdrucks oder bis zum Verlust seines Gültigkeitsbereichs.
Die Anzeige selbst wird beim Bewegen durch den Quelltext nicht aktualisiert, sondern muss durch die Schaltfläche Auswerten jeweils angestoßen werden. Anders als die Liste überwachter Ausdrücke, eignet sich also dieses Fenster nur zum punktuellen Auswerten bzw. Ändern von Ausdrücken und nicht zur dauerhaften Überwachung.
Durch die Schaltfläche Überwachen können wir den aktuellen Ausdruck der Liste überwachter Ausdrücke hinzufügen.

Aufruf-Stack

Im linken oberen Bereich des Debug-Layouts befindet sich die Darstellung aller aufgerufenen Routinen bis hin zur aktuellen Position. Der oberste Eintrag zeigt somit die Routine an, in der wir uns gerade befinden, der Eintrag darunter, durch welche Routine diese aufgerufen wurde. Das setzt sich dann nach unten fort. Die Aufrufreihenfolge, beginnend vom Start des Programms, wird also zeitlich von unten nach oben aufgebaut und ist nur dann einsehbar, wenn der Programmlauf durch einen Haltepunkt unterbrochen wurde.
Schauen wir uns den Aufruf-Stack unseres Programms, unterbrochen durch den Haltepunkt in Zeile 28 und fortgesetzt mit F7, beim Eintritt in die Funktion IstPrimzahl an:

bild7.png

Oben in der Liste sehen wir wie erwartet die Funktion IstPrimzahl. Wir erkennen ebenfalls den übergebenen Parameter, ausgewertet zum Betrachtungszeitpunkt. Würde dieser Parameter in der aufgerufenen Routine bearbeitet werden, so würde sich bei der schrittweisen Bewegung im Quelltext auch die Darstellung dieses Wertes im Aufruf-Stack ändern. In der zweiten Zeile wird die aufrufende Routine Primzahl gelistet, was unserem Hauptprogramm entspricht. Darunter sehen wir dann Initialisierungsarbeiten beim Programmstart, welche für uns, erkennbar durch den fehlenden blauen Punkt am Zeilenanfang, nicht im Quelltext erreichbar sind. Im Umkehrschluss können wir durch Doppelklick bzw. „Quelltext anzeigen“ im Kontextmenü des 2. Eintrags im Aufruf-Stack, die aufrufende, dann hervorgehobene Stelle im Quelltext lokalisieren. Hier gibt es einen Unterschied zu einer VCL-Anwendung, denn dort wird die auf den Aufruf folgende Zeile markiert. Zudem wäre dort der Aufruf-Stack deutlich umfangreicher, da in einer Formularanwendung erheblich mehr Initialisierungsarbeit geleistet werden muss.