10 (100)


Rozdział 10.
Wykrywanie błędów w aplikacjach


Jedną z ważnych cech zintegrowanego środowiska Delphi jest jego debugger. Narzędzie to umożliwia ustawianie punktów przerwań, podgląd stanu zmiennych, kontrolowanie obiektów, a także przeprowadzanie wielu innych operacji, dzięki którym można (względnie) szybko zorientować się, co dzieje się (lub nie dzieje) z badanym programem. Dobry debugger ma niebagatelne znaczenie dla efektywnego tworzenia aplikacji.

Tak się niestety składa, że sama czynność śledzenia programu i wyszukiwania błędów jest przez wielu programistów traktowana marginalnie. Ja sam, kiedy rozpocząłem programowanie dla Windows, przez długi czas ignorowałem debugger, gdyż byłem pochłonięty nauką samego programowania. Kiedy jednak odkryłem, jak cennym narzędziem jest dobry program wykrywania błędów, poczułem się trochę głupio z powodu unikania go przez tak długi czas. Czytając tę książkę dysponujesz luksusem uczenia się na moich błędach. Z tego rozdziału dowiesz się, jakie możliwości daje debugger.

Debugger wchodzący w skład środowiska IDE udostępnia kilka cech i narzędzi, które pomagają w pracy przy wykrywaniu błędów. W dalszej części omówione zostaną następujące zagadnienia:

Po co stosować debugger?

Najprościej mówiąc - po to, aby pomóc sobie przy wykrywaniu błędów w pracy programu. Niemniej jednak proces śledzenia służy nie tylko do odnajdywania i korygowania błędów - jest to również narzędzie wspomagające rozwój oprogramowania. Mimo, iż proces wykrywania błędów ma duże znaczenie, wielu programistów nie poświęca wystarczająco dużo czasu na przyswojenie sobie zasad korzystania z wszystkich cech zintegrowanego debuggera; w efekcie przysparzają oni sobie wielu kosztów i straty czasu, nie mówiąc już o frustracji wynikającej z trudnych do znalezienia błędów.

Generalnie, uruchomienie programu pod kontrolą debuggera rozpoczyna sesję wykrywania błędów; w środowisku z debuggerem zintegrowanym (jak np. Delphi) debugger ten aktywizowany jest już w momencie uruchomienia programu (co, jak wiadomo, następuje w wyniku naciśnięcia klawisza F9, lub wybrania opcji Run|Run menu głównego, bądź też kliknięcia w przycisk Run na pasku narzędzi.)

Elementy menu związane
z procesem śledzenia

Przed przystąpieniem do omawiania szczegółów procesu wykrywania błędów przyjrzyjmy się poleceniom menu głównego, związanym z debuggerem. Niektóre z tych poleceń należą do kategorii Run głównego menu, pozostałe znaleźć można w menu kontekstowym Edytora Kodu. Elementy menu kontekstowego Edytora Kodu (specyficzne dla debuggera) zestawione zostały w tabeli 10.1.

Tabela 10.1. Elementy menu kontekstowego Edytora Kodu związane ze śledzeniem programu

Element

Klawisz skrótu

Opis

Toggle Breakpoint

F5

Ustawia lub likwiduje punkt przerwania w bieżącej linii Edytora Kodu.

Run to Curso

F4

Uruchamia program i wykonuje go aż do
osiągnięcia linii kodu (w oknie edytora), w której znajduje się kursor.

Inspect

Alt+F5

Otwiera okno Inspektora Śledzenia
(Debug Inspector) dla obiektu wskazywanego przez kursor.

Goto Address

Ctrl+Alt+G

Umożliwia określenie adresu w programie, od którego wznowiony zostanie proces
wykonania programu.

Evaluate/Modify

Ctrl+F5

Umożliwia podgląd i/lub modyfikację zmiennej w trakcie pracy programu.

Add Watch at Cursor

Ctrl+F7

Dodaje zmienną wskazywaną przez kursor do listy wyrażeń testowych.

View CPU

Ctrl+Alt+C

Wyświetla okno kodu w postaci asemblera.

Menu Run zawiera kilka poleceń odnoszących się do programów pracujących pod kontrolą debuggera. Polecenia te pozwalają, między innymi, uruchomić program pod kontrolą debuggera, przerwać wykonanie programu pracującego pod debuggerem --> [Author:J] , a także określić parametry przekazywane do programu z poziomu wiersza poleceń. Niektóre polecenia tego menu są zdublowane w menu kontekstowym Edytora Kodu. Polecenia menu Run, kontrolujące operacje wykrywania błędów, zostały zestawione w tabeli 10.2.

Tabela 10.2. Elementy menu Run, kontrolujące operacje wykrywania błędów

Element

Skrót

Opis

Run

F9

Kompiluje program (jeżeli zachodzi taka potrzeba) i uruchamia go pod kontrolą
wbudowanego debuggera. Operacja równoważna kliknięciu na przycisku Run.

Parameters

(brak)

Umożliwia wpisanie parametrów wiersza poleceń dla aplikacji i przypisania aplikacji nadrzędnej podczas śledzenia biblioteki DLL.

Step Over

F8

Wykonuje linię kodu źródłowego, wskazywaną przez punkt wykonania, po czym zatrzymuje się w następnej linii kodu.

Trace Into

F7

Wchodzi do wnętrza procedury/funkcji napotkanej w punkcie wykonania.

Trace to Next Source Line

Shift+F7

Powoduje przejście punktu wykonania do następnej linii w kodzie źródłowym programu.

Run to Cursor

F4

Uruchamia program i wykonuje go aż do osiągnięcia linii kodu (w oknie edytora), w której znajduje się kursor.

Show Execution Point

(brak)

Wyświetla punkt wykonania programu w Edytorze Kodu, przewijając kod w oknie jeżeli jest to niezbędne. Działa tylko w stanie zatrzymania programu.

Program Pause

(brak)

Zatrzymuje wykonanie programu, kiedy tylko sterowanie osiągnie kod posiadający swą reprezentację w kodzie źródłowym.

Program Reset

Ctrl+F2

Bezwarunkowo zatrzymuje program i zwraca sterowanie do środowiska IDE Delphi.

Inspect

(brak)

Wyświetla okno dialogowe inspekcji,
umożliwiając wpisanie nazwy obiektu
przeznaczonego do nadzorowania.

Evaluate/Modify

Ctrl+F7

Wyświetla okno dialogowe
podglądu/modyfikacji.

Add Watch

Ctrl+F5

Wyświetla okno właściwości podglądu.

Add Breakpoint

(brak)

Wyświetla podmenu zawierające polecenia dodania punktu przerwania do kodu źródłowego, adresu, danej lub modułu.

W trakcie testowania swojej aplikacji będziesz często korzystał z tych poleceń, powinieneś więc również zaznajomić się z klawiszami skrótu operacji wykrywania błędów. Przyjrzyjmy się teraz punktom przerwania i możliwościom wykorzystania ich w programie.

Punkty przerwań

Kiedy uruchomisz swój program w zintegrowanym środowisku Delphi, zostanie on wykonany z pełną szybkością, zatrzymując się jedynie w miejscach, w których ustawione zostały punkty przerwań.

0x01 graphic

Punkt przerwania jest to znacznik nakazujący debuggerowi zatrzymanie procesu wykonania programu w chwili osiągnięcia tego znacznika.

Ustawianie i usuwanie punktów przerwań

Ustawienie punktu przerwania wymaga kliknięcia myszą na obszarze guttera na wysokości linii, w której chcesz zatrzymać wykonanie programu (gutterem nazywany jest szary pasek należący do lewej krawędzi okna Edytora Kodu). Ustawienie punktu przerwania jest sygnalizowane ikoną (czerwonym kołem) na pasku guttera oraz podświetleniem linii na czerwono. Ponowne kliknięcie na ikonie punktu przerwania spowoduje jego usunięcie. Punkt przerwania może być również ustawiony lub usunięty z poziomu klawiatury (klawisz F5) lub z poziomu menu kontekstowego Edytora Kodu.

0x01 graphic

Punkt przerwania może być ustawiony jedynie w linii, która posiada swe odzwierciedlenie w skompilowanym kodzie. Do nieprawidłowych operacji należy ustawianie punktów przerwań w liniach pustych, zawierających komentarz lub deklarację. Próby ustawienia punktu przerwania w jednej z takich linii nie są blokowane, ale powodują wystosowanie ostrzeżenia przez debugger. Ustawienie punktu przerwania w jednej z poniższych linii kodu, spowoduje wygenerowanie ostrzeżenia nieprawidłowego punktu przerwania:

{To jest komentarz, po którym następuje pusta linia.}

X : Integer; {Deklaracja}

Punkty przerwań mogą być ustawiane na dyrektywie end kończącej procedurę lub funkcję.

Jeżeli ustawisz punkt przerwania w nieprawidłowej linii, Edytor Kodu wyświetli ją na zielono (zakładając domyślne ustawienia kolorów), a ikona punktu na gutterze będzie zaciemniona.

Program pracujący pod kontrolą debuggera zachowuje się tak, jakby pracował całkowicie samodzielnie - aż do momentu, kiedy osiągnięty zostanie punkt przerwania. Kiedy to nastąpi, sterowanie wraca do środowiska IDE, a linia kodu źródłowego, zawierająca punkt przerwania, zostaje podświetlona. Jeżeli stosujesz domyślny ustawienia kolorów linia, na której zatrzymany został program, jest podświetlona na czerwono, ponieważ ten właśnie kolor wskazuje linię z ustawionym punktem przerwania.

0x01 graphic

Punkt wykonania wskazuje linię kodu źródłowego, która będzie wykonana w następnej kolejności

W miarę krokowego wykonywania programu, punkt wykonania jest podświetlany na niebiesko, a na gutterze okna edytora wyświetlana jest zielona strzałka. Linia podświetlona na niebiesko jest linią, która zostanie wykonana w następnej kolejności, kiedy tylko praca programu zostanie wznowiona.

0x01 graphic

Punkt wykonania programu jest podświetlony na niebiesko, o ile w linii tej nie został ustawiony punkt przerwania, w przeciwnym przypadku linia jest podświetlana na czerwono. Najbardziej precyzyjnym wskaźnikiem punktu wykonania jest zielona strzałka widoczna na gutterze, ponieważ jej obecność jest niezależna od koloru podświetlenia linii.

Po zatrzymaniu programu na punkcie przerwania możesz przyjrzeć się wartościom zmiennych, stosowi wywołań lub rozpocząć pracę krokową. Po dokonaniu inspekcji zmiennych i obiektów, możesz wznowić pracę programu, klikając na przycisku Run. Aplikacja rozpocznie normalną pracę aż do osiągnięcia kolejnego punktu przerwania.

0x01 graphic

Błędy popełnione w procesie kodowania często wykrywane są w wyniku zatrzymywania programu w punktach przerwania. Jeżeli w trakcie sesji wykrywania błędów dokonasz zmian w kodzie źródłowym, a następnie wybierzesz polecenie wznowienia pracy programu, środowisko IDE wyświetli okno dialogowe z zapytaniem, czy chcesz ponownie skompilować program. Jeżeli odpowiesz twierdząco, nastąpi przerwanie bieżącego procesu, kod źródłowy zostanie skompilowany, po czym nastąpi ponowne uruchomienie programu.

W wyniku takiego podejścia program nie ma szansy na normalne zakończenie swojej pracy, a w związku z tym - wykorzystywane przez niego zasoby w danym momencie mogą nie zostać zwolnione w sposób prawidłowy. Scenariusz taki niemal na pewno zaowocuje zagubieniem pewnej ilości pamięci. I chociaż programy Windows 95 i Windows NT radzą sobie znacznie lepiej z „gubieniem” zasobów, niż 16-bitowa wersja Windows, nadal wskazane jest zakończenie programu w sposób naturalny, a następnie zrekompilowanie go.

Okno listy punktów przerwań

Zintegrowane środowisko Delphi sprawuje kontrolę nad wszystkimi ustawianymi przez Ciebie punktami przerwań. Ich podgląd umożliwia okno listy punktów przerwań; aby wyświetlić tę listę, wybierz polecenie menu ViewDebug WindowsBreakpoints - otwarte zostanie okno, widoczne na rysunku 10.1.

Rysunek 10.1.

Okno listy punktów przerwań

0x01 graphic

Okno listy punktów przerwań składa się z czterech kolumn:

Rozmiar kolumn może być modyfikowany przez przeciąganie linii podziału między nagłówkami.

0x01 graphic

Kolumna Pass nie pokazuje liczby dokonanych już przejść przez punkt przerwania; jest to tylko warunek ilości przejść, jaki ustawiłeś dla punktu przerwania.

Menu kontekstowe listy punktów przerwań

Okno listy punktów przerwań posiada dwa menu kontekstowe. W tabeli 10.3 zestawione zostały elementy menu widoczne po kliknięciu prawym przyciskiem myszy na punkcie przerwania - w dalszej części rozdziału menu to będzie nazywane podstawowym menu kontekstowym; drugim ze wspomnianych menu zajmiemy się za chwilę.

0x01 graphic

Aby szybko przejść do edycji linii kodu, w której ustawiony jest punkt przerwania, kliknij dwukrotnie na danym punkcie przerwania w kolumnie nazwy pliku (Filename) okna listy punktów przerwań. Operacja taka jest równoważna wybraniu polecenia Edit Source z menu kontekstowego okna listy.

Drugorzędne menu kontekstowe wyświetlane jest w wyniku kliknięcia prawym przyciskiem myszy na obszarze okna listy w miejscu, w którym nie ma punktu przerwania. W skład tego menu wchodzą polecenia o nazwach: Add (Dodaj), Delete All (Usuń wszystkie), Disable All (Dezaktywuj wszystkie), Enable All (Udostępnij wszystkie) i Dockable (Włącz/wyłącz dokowanie). Nazwy elementów wskazują jednoznacznie na ich przeznaczenie, dlatego nie będą tutaj dokładniej omawiane.

Tabela 10.3. Podstawowe menu kontekstowe okna listy punków przerwań

Element

Opis

Enable

Uaktywnia lub dezaktywuje punkt przerwania. Kiedy punkt przerwania jest nieaktywny, jego ikona w oknie listy punktów przerwań, jest przyciemniona. Ikona punktu przerwania w kodzie źródłowym jest również przyciemniona, a linia kodu jest podświetlona na zielono, co wskazuje na nieaktywny stan punktu przerwania.

Delete

Usuwa punkt przerwania.

View Source

Powoduje wyświetlenie w Edytorze Kodu linii zawierającej wybrany punkt przerwania. (Okno listy pozostaje jednak nadal oknem pierwszoplanowym).

Edit Source

Umieszcza kursor w tej linii kodu źródłowego, w której ustawiony jest wybrany punkt przerwania; okno Edytora Kodu staje się oknem pierwszoplanowym.

Properties

Wyświetla okno dialogowe właściwości punktu przerwania.

Dockable

Określa, czy okno listy punktów przerwań jest dokowalne.

Uaktywnianie i dezaktywowanie punktów przerwań

Dezaktywacja punktu przerwania ma na celu jego tymczasowe anulowanie - punkt przerwania zachowywany jest wraz ze wszystkimi elementami swej definicji, podczas wykonywania programu jest on jednak ignorowany przez debugger. Ponowne aktywowanie punktu przerwania przywraca jego normalne funkcjonowanie. Jest to z pewnością znacznie wygodniejsze, niż usuwanie punktu przerwania i jego ponowne definiowanie.

Aby uaktywnić lub dezaktywować punkt przerwania, kliknij na nim prawym przyciskiem myszy w oknie listy i zaznacz opcję Enable menu kontekstowego.

Modyfikowanie punktów przerwań

Jeżeli chcesz zmodyfikować definicję punktu przerwania, wybierz z podstawowego menu kontekstowego polecenie Properties. Kiedy to zrobisz, otwarte zostanie okno właściwości punktu przerwania (Source Breakpoint Properties - rysunek 10.2).

Rysunek 10.2.

Okno właściwości punktu przerwania

0x01 graphic

Najczęstszym powodem modyfikacji właściwości punktu przerwania jest zdefiniowanie/ przedefiniowanie warunku - zagadnienie to jest omawiane w sekcji „Warunkowe punkty przerwań”.

Aby usunąć punkt przerwania, wybierz go w oknie listy punktów przerwań, a następni naciśnij przycisk Delete na klawiaturze. Jeżeli chcesz usunąć wszystkie punkty przerwań, kliknij prawym przyciskiem myszy i wybierz polecenie Delete All.

Zwykłe punkty przerwań

Zwykły punkt przerwania powoduje zawieszenie procesu wykonania programu przy każdorazowym osiągnięciu takiego punktu. Wszystkie punkty przerwań, jakie ustawiasz początkowo, domyślnie traktowane są jako zwykłe. Zwykły punkt przerwania nie wymaga szczególnego objaśnienia - po jego napotkaniu wykonanie programu zostaje wstrzymane, a debugger oczekuje na polecenia. W większości przypadków będziesz stosował właśnie zwykłe punkty przerwań. Warunkowe punkty przerwań przewidziane są dla sytuacji specjalnych, w przypadku których będziesz potrzebował większej kontroli nad procesem wykrywania błędów.

Warunkowe punkty przerwań dzielą się na dwie kategorie. Pierwsza z nich to tzw. punkty przerwań z wyrażeniem warunkowym. Wpisz wyrażenie warunkowe w pole Condition okna właściwości punktu przerwania (rysunek 10.2); wyrażenie to będzie wartościowane każdorazowo, gdy osiągnięty zostanie punkt przerwania - jeżeli jego wartością będzie prawda (True), wykonanie program zostanie zatrzymane, w przeciwnym wypadku punkt przerwania będzie ignorowany. Dla przykładu, spójrz na ostatni punkt przerwania w oknie listy, przedstawionym na rysunku 10.1. Punkt ten posiada wyrażenie warunkowe X > 20. Jeżeli zostanie on osiągnięty w pewnym momencie wykonania programu, zatrzymanie nastąpi tylko wówczas, gdy zmienna X będzie w tym momencie posiadać wartość większą od 20.

Innym typem punktu przerwania jest punkt przerwania z warunkiem ilości przejść. W tym przypadku wykonanie programu jest zatrzymywane jedynie wtedy, gdy punkt przerwania zostanie osiągnięty określoną ilość razy. Aby określić liczbę przejść punktu przerwania, otwórz jego okno właściwości i wpisz odpowiednią wartość w pole Pass Count. Jeżeli, przykładowo, ustawisz ilość przejść na wartość 3, wykonanie programu zostanie zatrzymane w punkcie przerwania przy jego trzecim napotkaniu.

0x01 graphic

Ilość przejść jest liczona względem jedynki, nie zera. Jak wskazuje poprzedni przykład, ilość przejść równa 3 oznacza, że punkt przerwania stanie się aktywny przy jego trzecim napotkaniu przez program.

Stosuj punkty przerwań z warunkiem ilości przejść wtedy, kiedy będziesz wymagał kilkakrotnego wykonania programu, przed jego zatrzymaniem i przejrzeniem zmiennych, przejściem do pracy krokowej, lub innej pracy związanej z wykrywaniem błędów.

0x01 graphic

Warunkowe punkty przerwań spowalniają normalny proces wykonania programu, ponieważ przy każdym napotkaniu tego typu punktu musi nastąpić sprawdzenie warunku. Jeżeli w trakcie procesu wykrywania błędów Twój program działa ociężale, przejrzyj listę punktów przerwań i sprawdź czy nie ma na niej warunkowych punktów przerwań, o których istnieniu zapomniałeś.

0x01 graphic

Niekiedy jednak fakt spowalniania wykonania programu przez warunkowe punkty przerwań może działać na Twoją korzyść. Jeżeli chcesz przyjrzeć się jakiemuś procesowi w zwolnionym tempie, umieść w jego kodzie jeden lub więcej punktów przerwań. Ustaw takie warunki, które nigdy nie zostaną spełnione, przez co program ulegnie spowolnieniu, ale nigdy nie zostanie zatrzymany.

Polecenie Run to Cursor

Polecenie to, dostępne w menu Run lub w menu kontekstowym Edytora Kodu, wykonuje program aż do momentu, kiedy osiągnięta zostanie linia kodu zawierająca kursor. W tym miejscu program zostaje zatrzymany tak, jak gdyby osiągnięty został punkt przerwania.

Polecenie to można traktować jako tymczasowy punkt przerwania. Korzystaj z tej komendy zamiast używania punktu przerwania, jeżeli chcesz bezwarunkowo zatrzymać program w danym miejscu. Wystarczy, że umieścisz kursor w linii, w której ma nastąpić przerwanie programu, i wybierzesz polecenie Run to Cursor (lub naciśniesz klawisz F4), a debugger zachowa się tak, jakbyś w tym miejscu umieścił punkt przerwania. Dzięki zastosowaniu tego polecenia jesteś zwolniony z obowiązku usuwania punktu przerwania po zakończeniu testowania określonej części kodu.

Podgląd zmiennych

Co więc należy zrobić kiedy program zatrzyma się na punkcie przerwania? Zazwyczaj celem zatrzymania programu jest chęć przyjrzenia się zawartości jednej lub kilku zmiennych. Możesz chcieć upewnić się, że określona zmienna zawiera oczekiwaną wartość lub po prostu sprawdzić wartość pewnej zmiennej, jeżeli wyleciała Ci ona z głowy.

Funkcja listy wyrażeń testowych (Watch List) jest prosta: pozwala ona śledzić wartości zmiennych. Programiści zwykle pomijają to proste, lecz istotne narzędzie, ponieważ nie poświęcają dostatecznej ilości czasu na pełne zrozumienie debuggera. Lista wyrażeń testowych nie narzuca ograniczenia na liczbę zmiennych, jakie można przeglądać. Rysunek 10.3 przedstawia okno listy wyrażeń testowych w trakcie sesji debuggera.

Rysunek 10.3.

Lista wyrażeń testowych w działaniu

0x01 graphic

Elementami listy są nazwy zmiennych, po których następuje ich bieżąca wartość. Sposób wyświetlania zmiennych zależy od typu danych jakie przechowują oraz bieżących ustawień wyświetlania danego elementu. Do listy wyrażeń testowych wrócę za chwilę, przedtem jednak umówię cechę, która znacznie ułatwia proces podglądania zmiennych.

Wartościowanie wyrażenia w formie podpowiedzi

Zarówno debugger, jak i Edytor Kodu posiadają cechę, która znacznie ułatwia sprawdzanie wartości zmiennej. Cecha ta - wartościowanie wyrażeń przez podpowiedź (Tooltip expression evaluation) - jest standardowo włączona, jej użycie nie wymaga więc żadnych dodatkowych czynności z Twojej strony. Wyłączenie tego mechanizmu jest możliwe poprzez stronę Code Insight opcji środowiska (Environment Options), o której mowa była w poprzednim rozdziale.

Czym właściwie jest wartościowanie wyrażeń przez podpowiedź? W przypadku zwykłych pól danych (Integer, Char, Byte, String, itd.) wyświetlana jest ich rzeczywista wartość. Jeśli chodzi o obiekty tworzone dynamicznie (np. egzemplarze klas), okno podpowiedzi wyświetla adres obiektu w pamięci. W przypadku rekordu wyświetlane są wszystkie jego elementy (pola). Przykład wartościowania pól rekordu przedstawiony został na rysunku 10.4.

Rysunek 10.4.

Wartościowanie pól rekordu przez podpowiedź

0x01 graphic

0x01 graphic

Czasami działanie wartościowania może być odbierane jako nieprawidłowe. Jeżeli, dla przykładu, umieścisz kursor nad zmienną, która występuje poza obszarem widoczności swej definicji, podpowiedź nie pojawi się.

Ponadto, zmienne optymalizowane przez kompilator mogą nie wskazywać prawidłowych wartości. Optymalizacja była omawiana w poprzednim rozdziale i będzie o niej jeszcze mowa w dalszej części rozdziału.

Innym przypadkiem, kiedy wartościowanie przez podpowiedź nie działa, jest wnętrze bloku with. Dla przykładu rozważmy następujący fragment kodu:

with Point do begin

X:= 20;

Y:= 50;

Label1.Caption := IntToStr(X);

end;

Po umieszczeniu kursora nad zmienną X, podpowiedź nie wyświetliłaby żadnej wartości, ponieważ X należy do zmiennej występującej w wyrażeniu with (Point). Jeżeli jednak umieścisz kursor nad zmienną Point, wyświetlona zostanie jego wartość (łącznie z polem X).

Menu kontekstowe listy wyrażeń testowych

Jak wszystkie omawiane do tej pory okna Delphi, również i okno listy wyrażeń testowych posiada własne menu kontekstowe. Jego polecenia zostały zestawione w tabeli 10.4.

Tabela 10.4. Polecenia menu kontekstowego dla listy wyrażeń testowych

Polecenie

Opis

Edit Watch

Umożliwia edycję elementu listy poprzez okno właściwości wyrażeń testowych (Watch Properties).

Add Watch

Dodaje nowy element do listy wyrażeń testowych.

Enable Watch

Uaktywnia element listy.

Disable Watch

Dezaktywuje element listy.

Delete Watch

Usuwa element listy.

Enable All Watches

Uaktywnia wszystkie elementy na liście.

Disable All Watches

Dezaktywuje wszystkie elementy na liście.

Delete All Watches

Usuwa wszystkie elementy z listy.

Stay on Top

Wymusza pozostawanie okna listy wyrażeń na pierwszym planie środowiska IDE.

Break When Changes

Kiedy wartość zmiennej umieszczonej na liście ulegnie zmianie, debugger przerwie program. Aktywność tej opcji w stosunku do danej zmiennej jest sygnalizowana podświetleniem jej na czerwono.

Dockable

Określa, czy okno listy wyrażeń testowych jest dokowalne.

Polecenia Edit Watch i Add Watch wywołują okno właściwości podglądu. Przyjrzyjmy się z bliska temu oknu.

Okno właściwości podglądu

Celem tego okna jest dodanie lub edycja podglądanego elementu. Wygląd okna właściwości, podczas edycji zmiennej Buff, został przedstawiony na rysunku 10.5.

Rysunek 10.5.

Okno właściwości podglądu

0x01 graphic

Pole Expression, widoczne w górnej części okna, służy do wprowadzenia nazwy zmiennej przeznaczonej do edycji lub dodania do listy. Pole to jest listą rozwijalną umożliwiającą również wybór zmiennych podglądanych już wcześniej.

Pole Repeat count znajduje zastosowanie w przypadku podglądania tablic. Dla przykładu, załóżmy że posiadasz tablicę 20 elementową. Aby śledzić zawartość pierwszych dziesięciu elementów tej tablicy, powinieneś wpisać w pole Expression pierwszy element tablicy (np. Tablica[0]), zaś w pole Repeat Count - wartość 10; efektem tego byłoby umieszczenie pierwszych dziesięciu elementów tablicy na liście wyrażeń testowych.

0x01 graphic

Jeżeli do listy wyrażeń testowych dodasz tylko nazwę tablicy, wyświetlane zostaną wszystkie jej elementy. Skorzystaj z pola Repeat Count, aby ograniczyć liczbę jej wyświetlanych elementów.

Pole Digits znajduje zastosowanie jedynie w przypadku obserwowania liczb zmiennoprzecinkowych. Dzięki niemu jesteś w stanie określić liczbę cyfr znaczących wartości wyświetlanej na liście wyrażeń testowych. Wyświetlana wartość nie jest obcinana, lecz zaokrąglana. Kolejne pole tego okna - Enabled - decyduje, czy modyfikowany element listy ma być aktywny.

Pozostała część okna właściwości podglądu składa się z różnorodnych opcji wyświetlania. Każdy typ danych cechuje się domyślnym sposobem wyświetlania, stosowanym w przypadku wybrania opcji Default. Wybierz inną opcję, aby wyświetlić wartość w innej formie. Na rysunku 10.6 w oknie listy wyrażeń testowych widoczne są dwie zmienne wyświetlane przy zastosowaniu różnych opcji reprezentacji na ekranie: Buff jest tablicą typu znakowego, natomiast I jest zmienną całkowitą (integer).

Rysunek 10.6.

Okno wyrażeń testowych z wyświetlające dane przy różnych opcjach reprezentacji

0x01 graphic

Aby zmodyfikować element listy, kliknij na nim w obszarze okna listy i wybierz polecenie menu kontekstowego Edit Watch. Edycji elementu można również dokonać klikając na nim dwukrotnie. Efektem obu tych czynności jest otwarcie okna właściwości wyrażeń testowych (Watch Properties), które umożliwia wprowadzenie dowolnych modyfikacji.

0x01 graphic

Najszybszy sposób przejścia do edycji elementu to dwukrotne kliknięcie jego nazwy w obszarze okna listy wyrażeń testowych.

Uaktywnianie i dezaktywowanie
elementów listy wyrażeń testowych

Podobnie jak w przypadku punktów przerwań, również indywidualne elementy listy wyrażeń testowych mogą być uaktywniane lub dezaktywowane. Element nieaktywny jest przyciemniony, a jego wartość wskazuje łańcuch <disabled>.

Aby uczynić element nieaktywnym, kliknij na nim w obszarze okna listy wyrażeń i wybierz polecenie Disable Watch. Ponowne uaktywnienie elementu następuje w wyniku wybrania polecenia Enable Watch.

0x01 graphic

Powodem do dezaktywacji elementów listy wyrażeń testowych może być chwilowy brak potrzeby ich obserwowania. Przy dużej liczbie aktywnych elementów listy wyrażeń testowych następuje zauważalne spowolnienie pracy programu; dzieje się tak dlatego, że zmienne na tej liście muszą być uaktualniane przy każdorazowym wykonaniu linii programu.

Dodawanie zmiennych do listy wyrażeń testowych

Istnieje kilka sposobów na dodanie elementu do listy wyrażeń testowych. Najszybszy z nich to kliknięcie na nazwie zmiennej w oknie edytora i wybranie polecenia Add Watch at Cursor z menu kontekstowego Edytora Kodu lub wciśnięcie kombinacji klawiszy Ctrl+F5. W wyniku tego element zostanie dodany do listy wyrażeń testowych; w miarę potrzeby można dokonać edycji jego opcji wyświetlania.

Poprzednia metoda wymagała zlokalizowania zmiennej w kodzie, czego można uniknąć wybierając polecenie menu RunAdd Watch. Kiedy wyświetlone zostanie okno właściwości wyrażenia testowego (Watch Properties), wpisz nazwę zmiennej, którą chcesz dodać do listy i kliknij na przycisku OK.

0x01 graphic

Mimo, że do listy wyrażeń testowych można dodać zmienną reprezentującą obiekt dowolnej klasy, informacja wyświetlana w takim przypadku okaże się mało użyteczna. Do przejrzenia wszystkich pól klasy powinieneś wykorzystać Inspektor Obiektów, o którym za chwilę.

Użytkowanie listy wyrażeń testowych

Kiedy program zostanie zatrzymany w punkcie przerwania, lista wyrażeń testowych wyświetla bieżące wartości wszystkich zmiennych w niej występujących. Jeżeli w danej chwili lista jest niewidoczna, można otworzyć ją poleceniem menu ViewDebug Windows Watches.

0x01 graphic

Zadokuj listę wyrażeń testowych w dolnej części okna Edytora Kodu, dzięki czemu będzie ona zawsze widoczna podczas pracy krokowej.

W określonych warunkach zamiast wartości zmiennej wyświetlony zostanie komunikat. Przykładowo, jeżeli zmienna znajdzie się poza zasięgiem definicji lub nie zostanie znaleziona, obok jej nazwy na liście wyrażeń testowych wyświetlony zostanie komunikat Undeclared identifier: `X'. Jeżeli program nie jest uruchomiony, bądź jego zatrzymanie nie wynika z punktu przerwania, obok wszystkich elementów listy pojawi się komunikat: [process not accessible]. Element nieaktywny zostanie oznaczony komunikatem <disabled>. W zależności od bieżącego stanu aplikacji lub określonej zmiennej mogą pojawić się również inne komunikaty.

Jak wspomniałem w poprzednim rozdziale, niekiedy dostęp do zmiennej X może okazać się niemożliwy ze względu na optymalizację kodu przeprowadzaną przez kompilator - co jest przejawem znanej zasady, że w procesie śledzenia kodu optymalizacja może programiście co najwyżej przeszkadzać. Rozsądnym rozwiązaniem jest więc rezygnacja z optymalizacji na tym etapie (osiąga się to, wyłączając opcję Optimization na stronie Compiler okna właściwości projektu).

Pamiętaj również, że zmienne, którym nie zostały przypisane wartości początkowe, będą posiadały wartości przypadkowe aż do momentu ich inicjalizacji.

0x01 graphic

Niejako ubocznym zastosowaniem listy wyrażeń testowych może służyć wykorzystanie jej w roli szybkiego konwertera liczb dziesiętnych na szesnastkowe i odwrotnie. Aby przekonwertować liczbę szesnastkową na dziesiętną, wybierz polecenie menu RunAdd Watch, wpisz wartość szesnastkową w pole Expression i kliknij na przycisku OK; w oknie listy wyrażeń testowych wyświetlone zostaną obydwie wartości - szesnastkowa i dziesiętna. Konwersja liczby dziesiętnej na szesnastkową przebiega w identyczny sposób, z jednym wyjątkiem: kliknij na przycisku opcji Hexadecimal, aby zmienić typ wyświetlanej wartości. Ponieważ pole Expression akceptuje dowolne wyrażenia matematyczne, możesz również użyć listy wyrażeń testowych jako kalkulatora liczącego w systemie szesnastkowym. Nie ma żadnych przeszkód w mieszaniu wartości szesnastkowych i dziesiętnych. Jedyną wadą takiego rozwiązania jest konieczność zatrzymania aplikacji w punkcie przerwania, aby można było w ogóle skorzystać z listy wyrażeń testowych.

W celu lepszego zrozumienia roli listy wyrażeń testowych w procesie śledzenia programu wykonaj następujące ćwiczenie:

  1. Stwórz nową aplikację i umieść przycisk w formularzu. Zmień właściwość Name przycisku na WatchBtn, a jego właściwość Caption na wartość Test Podglądu. Zmień właściwość Name formularza na DebugMain, a właściwość Caption na - cokolwiek sobie zażyczysz.

  2. Kliknij podwójnie na przycisku, aby wyświetlić jego procedurę obsługującą zdarzenie OnClick w Edytorze Kodu. Zmodyfikuj tę funkcję zgodnie z kodem przedstawionym poniżej:

procedure TForm1.Button1Click(Sender: TObject);

var

S : string;

X, Y : Integer;

begin

X := Width;

S := IntToStr(X);

Y := Height;

X := X * Y;

S := IntToStr(X);

X := X div Y;

S := 'X = ' + IntToStr(X);

Width := X;

Height := Y;

end;

  1. Zapisz projekt. Modułowi nadaj nazwę DbgMain, a projektowi - DebugTst.

  2. Ustaw punkt przerwania w pierwszej linii za dyrektywą begin we wnętrzu procedury obsługującej zdarzenie OnClick. Uruchom program.

  3. Kliknij na przycisku Test Podglądu. Debugger zatrzyma się w punkcie przerwania, a na pierwszym planie pojawi się środowisko IDE wraz z Edytorem Kodu.

  4. Do listy wyrażeń testowych dodaj zmienne S, X i Y. (Początkowo zmienne X i Y będą niedostępne ze względu na optymalizację, ale nie przejmuj się tym.)

  5. Przywołaj na ekran listę wyrażeń testowych i Edytor Kodu, rozmieść ich okna tak, aby oba były widoczne (Możesz zadokować okno listy wyrażeń testowych u dołu Edytora Kodu.)

  6. Uczyń okno edytora oknem aktywnym, naciśnij klawisz F8 powodując w ten sposób wykonanie linii kodu. Linia kodu zostaje wykonana, a punkt wykonania zostaje przeniesiony do linii następnej. W tej chwili zmienna X wykazuje konkretną wartość.

  7. Przejdź krokowo przez program naciskając kolejno klawisz F8. Obserwuj wartości zmiennych raportowane przez okno wyrażeń testowych.

  8. Kiedy punkt wykonania osiągnie ostatnią linię w metodzie, kliknij na przycisku Run paska narzędzi, aby wznowić pracę programu.

Kliknij dowolną ilość razy na przycisku Test Podglądu aby poczuć, w jaki sposób działa lista wyrażeń testowych. Poeksperymentuj z innymi ustawieniami podglądu.

0x01 graphic

Kod w powyższym przykładzie pobiera wartości dwóch właściwości formularza: Width i Height, dokonuje pewnych obliczeń, a następnie przypisuje właściwościom Width i Height wartości, jakie miały one na początku. W efekcie nic się nie zmienia, ale istnieje dobry powód aby przypisać wartości właściwościom Width i Height na końcu metody.

Jeżeli nie zrobisz czegoś ze zmiennymi X i Y, nie będziesz mógł dokonać ich inspekcji, ponieważ ze względu na optymalizację ich podgląd stanie się niemożliwy. Rzecz w tym, iż kompilator jest w stanie spojrzeć w przód i przekonać się, które ze zmiennych nie są nigdy używane, a więc można się ich pozbyć. Wykorzystanie wartości zmiennych X i Y na końcu metody zapobiega wyeliminowaniu ich przez kompilator w procesie optymalizacji.

Podnosiłem tę kwestię już kilkakrotnie, ale chcę się upewnić że będziesz w stanie zrozumieć w jaki sposób działa kompilator optymalizujący. Kiedy rozpoczniesz poszukiwanie błędów w swoich aplikacjach, wiedza ta pomoże Ci uniknąć frustracji związanej z pojawianiem się komunikatów typu Variable `X' inaccessible here do to optimization („zmienna X niedostępna ze względu na optymalizację”) w oknie listy wyrażeń testowych.

Inspektor Śledzenia

0x08 graphic
Inspektor Śledzenia jest nową cechą Delphi 4. Mówiąc wprost, Inspektor Śledzenia umożliwia przeglądanie złożonych struktur takich, jak klasy czy rekordy. Istnieje również możliwość przeglądania danych typu prostego, (np. całkowitego, tablicy znakowej itp.), chociaż w ich przypadku lepiej jest skorzystać z okna listy wyrażeń testowych. Inspektor Śledzenia staje się najbardziej użyteczny w przypadku obiektów i rekordów.

0x01 graphic

Wykorzystanie Inspektora Śledzenia jest możliwe tylko w trakcie zatrzymania programu pod kontrolą debuggera.

Aby dokonać inspekcji obiektu, kliknij na jego nazwie w pliku kodu źródłowego i wybierz polecenie Inspect z menu kontekstowego Edytora Kodu (lub naciśnij kombinację klawiszy Alt+F5). Ewentualnie możesz również wybrać polecenie menu RunInspect.

Okno Inspektora Śledzenia zawiera szczegółowe informacje na temat wyświetlanego obiektu. Jeżeli obiekt jest typu prostego, wyświetlana jest jego bieżąca wartość (szesnastkowo i dziesiętnie w przypadku typu numerycznego), a na pasku statusu okna umieszczany jest typ zmiennej. Jeżeli przykładowo śledzisz wartość typu całkowitego, na pasku statusu pojawi się napis Integer. U szczytu okna znajduje się lista rozwijalna, zawierająca początkowo opis podglądanego obiektu.

Jeżeli przeglądany obiekt jest klasą, Inspektor Śledzenia będzie wyglądał mniej więcej tak, jak przedstawia to rysunek 10.7.

Rysunek 10.7.

Inspektor Śledzenia kontroluje klasę

0x01 graphic

Aby lepiej zrozumieć ideę Inspektora Śledzenia, wykonaj poniższe ćwiczenie:

  1. Załaduj program DebugTst, który stworzyłeś wcześniej (o ile nie jest on już załadowany).

  1. Umieść punkt przerwania gdzieś we wnętrzu metody WatchBtnClick.

  2. Uruchom program i kliknij na przycisku Test Podglądu; program zatrzyma się w miejscu, w którym umieściłeś punkt przerwania.

  3. Wybierz polecenie menu RunInspect. Wyświetlone zostanie okno dialogowe inspekcji.

  4. W pole Expression wpisz wartość Self i naciśnij przycisk OK.

  5. Wyświetlone zostaje okno dialogowe Inspektora Śledzenia, umożliwiając przejrzenie pól danych formularza.

0x01 graphic

Korzystanie ze wskaźnika Self jest możliwe jedynie we wnętrzu metody danej klasy. Jeżeli ustawisz punkt przerwania w zwykłej funkcji, a następnie spróbujesz przejrzeć obiekt Self, otrzymasz komunikat błędu twierdzący, iż Self jest nieprawidłowym symbolem. W powyższym przykładzie Self odnosi się do głównego formularza aplikacji.

Strony Inspektora Śledzenia

Podczas inspekcji klas okno Inspektora Śledzenia składa się z trzech stron. Na początku znajdują się elementy należące do klasy przodka. Pola należące do przeglądanej klasy znajdują się u dołu listy. Istnieje możliwość wyłączenia informacji dotyczącej klasy przodka. W tym celu kliknij prawym przyciskiem myszy i wybierz polecenie Show Inherited z otwartego menu kontekstowego Inspektora Śledzenia.

Korzystając z klawiszy kursora klawiatury możesz poruszać się w górę i w dół po polach klasy, jednym spojrzeniem możesz określić typ każdego pola (spoglądaj na pasek statusu Inspektora Śledzenia). Żeby móc w przyszłości nadzorować określone pole danych, wystarczy dwukrotnie kliknąć na jego wartości. W wyniku tej operacji otwarte zostanie drugie okno Inspektora Śledzenia zawierające wybrane pole danych. Można otworzyć wiele okien Inspektora Śledzenia jednocześnie.

Okno o nazwie Methods wyświetla metody należące do klasy. W niektórych przypadkach okno to nie jest wyświetlane (np. podczas przeglądania prostych typów danych). Na pasku statusu wyświetlana jest deklaracja wybranej metody.

Na stronie Properties wyświetlane są właściwości przeglądanej klasy. Przeglądanie właściwości klasy ma słabe uzasadnienie (informacje tam zawarte nie są zbyt użyteczne). W większości przypadków będziesz w stanie znaleźć to, czego szukasz przeglądając pola danych związanych z określoną właściwością na stronie Data.

0x01 graphic

Strony Methods i Properties Inspektora Śledzenia są dostępne jedynie podczas przeglądania klasy. W przypadku prostych typów danych wyświetlana jest jedynie strona danych (Data).

0x01 graphic

Jeżeli chcesz, aby okno Inspektora Śledzenia znajdowało się zawsze na wierzchu - przed Edytorem Kodu - zaznacz pole wyboru Inspectors stay on top na stronie Debugger okna ustawień środowiska (Environment Options).

Menu kontekstowe Inspektora Śledzenia

Menu kontekstowe Inspektora Śledzenia zawiera kilka poleceń, które umożliwiają pracę zarówno z Inspektorem Śledzenia, jak i indywidualnymi zmiennymi. Na przykład, zamiast otwierać nowe okno dla kolejnych obiektów, można kliknąć prawym przyciskiem myszy i wybrać polecenie Descent w wyniku którego bieżący obiekt w Inspektorze Śledzenia zostanie zastąpiony wybranym obiektem. Jeżeli na przykład śledzisz zachowanie się formularza z przyciskiem o nazwie Button1, możesz wybrać ten przycisk w oknie Inspektora Śledzenia, a następnie użyć polecenia Descent. W tym momencie Inspektor rozpocznie nadzorowanie obiektu Button1. Metoda ta ma dodatkową zaletę: środowisko IDE utrzymuje listę obiektów, które były przeglądane. Aby cofnąć się do obiektu, który był już przeglądany wcześniej, wystarczy wybrać go z listy w górnej części okna Inspektora Śledzenia, co spowoduje uaktywnienie go w oknie Inspektora.

Polecenie Change menu kontekstowego Inspektora Śledzenia pozwala na modyfikację wartości zmiennej.

0x01 graphic

Zachowaj szczególną ostrożność podczas modyfikacji zmiennych za pomocą Inspektora Śledzenia. Modyfikacja niektórych pól danych lub wprowadzenie nieprawidłowej wartości do danej zmiennej może sprawić, że program „wysypie się”.

Polecenie Inspect umożliwia otwarcie drugiego okna Inspektora Śledzenia dla elementu wskazanego przez kursor - służy do tego polecenie New Expression menu kontekstowego.

Element menu kontekstowego o nazwie Show Inherited jest przełącznikiem określającym, jak wiele informacji powinno być wyświetlanych w oknie Inspektora Śledzenia. Kiedy pole to jest aktywne, Inspektor Śledzenia pokazuje wszystkie elementy klasy będącej przedmiotem inspekcji oraz klasy będącej jej bezpośrednim przodkiem. Przy wyłączonej opcji Show Inherited wyświetlane są tylko elementy charakterystyczne dla samej klasy przeglądanej, co powoduje przyspieszenie pracy Inspektora Śledzenia, ponieważ ma on mniej informacji do wyświetlania.

0x01 graphic

Jeżeli nie pamiętasz typu elementu, który chcesz obejrzeć, kliknij na nim w chwili zatrzymania programu w punkcie przerwania i wciśnij kombinację klawiszy Alt+F5, powodując wyświetlenie okna Inspektora Śledzenia; typ pola możesz przy okazji odczytać z paska statusu okna Inspektora.

Inne narzędzia procesu wykrywania błędów

Oprócz narzędzi przedstawionych do tej pory, Delphi posiada również inne przeznaczone do tropienia błędów. Niektóre z nich są z natury są bardzo zaawansowane i stosowane rzadziej niż pozostałe, niemniej jednak w rękach doświadczonego programisty stają się niezwykle użyteczne.

Okno Evaluate/Modify

Okno dialogowe Evaluate/Modify umożliwia śledzenie bieżącej wartości zmiennej oraz jej modyfikację (w miarę potrzeby). Umożliwia to testowanie zachowania się programu przy różnych parametrach, bez potrzeby rekompilowania go za każdym razem. Rysunek 10.8 przedstawia okno Evaluate/Modify w trakcie inspekcji przykładowej zmiennej.

Rysunek 10.8.

Okno dialogowe Evaluate/Modify

0x01 graphic

0x01 graphic

Pasek narzędzi okna Evaluate/Modify może wyświetlać duże lub małe ikony przycisków. Domyślnie wyświetlane są małe przyciski. Nie posiadają one podpisów, więc aby przekonać się o przeznaczeniu którejś z nich, trzeba naprowadzić na nią kursor myszy i przeczytać zawartość podpowiedzi. Aby wyświetlić duże ikony (z podpisami - jak na rysunku 10.8), przeciągnij pasek rozmiaru w dół, bezpośrednio poniżej paska narzędzi.

Okno Evaluate/Modify działa podobnie do listy wyrażeń testowych i Inspektora Śledzenia. Aby określić wartość zmiennej, wystarczy kliknąć na niej prawym przyciskiem myszy w kodzie źródłowym i wybrać polecenie Evaluate/Modify z menu kontekstowego Edytora Kodu. Jeżeli chcesz wpisać zmienną nie występującą w aktualnie dostępnym kodzie, wybierz polecenie menu RunEvaluate/Modify, a następnie wpisz nazwę zmiennej, która Cię interesuje.

Miejscem, w które należy wpisać nazwę zmiennej lub wyrażenie do wartościowania jest pole Expression. Wartość wyrażenia zostanie obliczona po kliknięciu przycisku Evaluate (lub naciśnięciu klawisza Enter), a wynik pojawi się w polu Result.

0x01 graphic

Okno dialogowe Evaluate/Modify może służyć jako szybki kalkulator. Akceptowane są wyrażenia w formie matematycznej składające się zarówno z liczb dziesiętnych, jak i szesnastkowych (a także ich kombinacje). Np. jeżeli w pole Evaluate wpiszesz wyrażenie

$400 - 256

i naciśniesz klawisz Enter, w polu Result pojawi się wynik 768.

W pole Evaluate można również wpisać wyrażenie typu logicznego i sprawdzić jego prawdziwość w polu Result. Jeżeli np. wpiszesz wyrażenie

20 * 20 = 400

w polu Result pojawi się wartość True (prawda). Warunkiem działania okna Evaluate/Modify jest zatrzymanie programu w punkcie przerwania.

Jeżeli chcesz zmienić wartość zmiennej, wpisz nową wartość w pole New Value i kliknij na przycisku Modify. Kiedy klikniesz na przycisku Run, aby wznowić pracę programu (lub będziesz kontynuował pracę krokową), zmienna będzie zawierać tę nową wartość.

0x01 graphic

W przeciwieństwie do okna wyrażeń testowych i Inspektora Śledzenia okno Evaluate/Modify nie jest automatycznie aktualizowane w trakcie pracy krokowej. Jeżeli zmienna w oknie Evaluate/Modify zostanie zmodyfikowana w kodzie programu, to aby zobaczyć rezultat tej modyfikacji będziesz musiał ponownie kliknąć na przycisku Evaluate. Takie rozwiązanie ma jedną podstawową zaletę: mechanizm pracy krokowej działa szybciej, ponieważ debugger nie musi wyznaczać śledzonego wyrażenia po każdym kroku programu (tak jak ma to miejsce w przypadku Inspektora Śledzenia i okna wyrażeń testowych). Typowy sposób wykorzystania okna Evaluate/Modify to wartościowanie wyrażenia i natychmiastowe zamknięcie okna.

Okno Call Stack

Okno Call Stack umożliwia śledzenie stosu wywołań procedur i funkcji. W celu jego otwarcia wybierz polecenie menu ViewDebug WindowsCall Stack. W oknie tym wyświetlana jest lista funkcji i procedur, jakie wywołane zostały przez program - w porządku wynikającym z kolejności ich wywoływania. Procedura lub funkcja wywołana jako ostatnia znajduje się na szczycie listy.

Podwójne kliknięcie na nazwie metody w oknie Call Stack spowoduje przejście do linii kodu źródłowego tej metody, o ile metoda ta znajduje się w Twoim programie. W przypadku funkcji i procedur których, kod źródłowy jest niedostępny (np. metody VCL), okno Call Stack zawiera jedynie adres i nazwę modułu, w którym zlokalizowana jest dana procedura. Podwójne kliknięcie funkcji lub procedury, dla której nie istnieje kod źródłowy, spowoduje otwarcie okna deasemblacji omawianego poniżej.

Przeglądanie kolejności wywołań jest najbardziej pomocne po wystąpieniu błędu naruszenia ochrony dostępu (Access Violation). Przejrzenie stosu wywołań umożliwia określenie miejsca, do którego doszedł program przed wystąpieniem błędu. Wiedza na temat tego miejsca jest często pierwszym krokiem w analizie przyczyny nieprawidłowego zachowania się programu.

0x01 graphic

Jeżeli lista kolejności wywołań zawiera pozornie bezsensowne informacje, może być to efekt uszkodzenia stosu wywołań. Uszkodzony stos wywołań zwykle wskazuje na przepełnienie stosu lub pamięci. W aplikacjach 32-bitowych przepełnienie stosu nie jest tak często spotykane, jak w aplikacjach 16-bitowych, niemniej jednak może się zdarzyć.

Okno deasemblacji

0x08 graphic
Okno deasemblacji zostało oficjalnie udokumentowane w Delphi 4 - w poprzednich wersjach Delphi można je było uzyskać poprzez odpowiednie modyfikacje w Rejestrze. Obecnie okno to jest oficjalną częścią Delphi i może zostać otwarte poleceniem menu ViewDebug WindowsCPU (lub z klawiatury Ctrl+Alt+C).

Okno deasemblacji umożliwia przeglądanie programu na poziomie instrukcji asemblera. Korzystając z tego widoku można wykonywać pracę krokową po jednej instrukcji procesora.

Można również wykonać program w normalnym tempie do określonej instrukcji asemblera - podobnie, tak jak w przypadku „zwykłego kodu” program wykonywany był do określonej linii programu pascalowego. Okno deasemblera składa się z pięciu zakładek: disassembly, register, flags, raw stack i dump; każda zakładka posiada własne menu kontekstowe. Efektywne wykorzystanie okna procesora wymaga dobrej znajomości asemblera.

Polecenie Go to Address

Jest to kolejne zaawansowane narzędzie procesu wykrywania błędów. W przypadku nieoczekiwanego zakończenia programu Windows wyświetla komunikat błędu wskazując adres miejsca, w którym wystąpiła nieprawidłowa sytuacja. Dzięki poleceniu Go to Address możesz spróbować dotrzeć do miejsca w programie, gdzie nastąpiło jego załamanie. W przypadku gdy nastąpi błąd naruszenia ochrony dostępu, Windows wygeneruje okno komunikatu zbliżone do tego z rysunku 10.9.

Rysunek 10.9.

Okno Windows z komunikatem naruszenia ochrony dostępu

0x01 graphic

Kiedy ujrzysz taki komunikat, zapisz adres, w którym wystąpił błąd ochrony, a następnie wybierz polecenie menu kontekstowego Edytora Kodu - DebugGo to Address. Wyświetlone zostanie okno dialogowe przejścia do wskazanego adresu; wpisz w pole Address zapisany wcześniej adres wystąpienia błędu.

Kiedy klikniesz na przycisku OK, debugger spróbuje znaleźć miejsce w kodzie źródłowym, w którym wystąpił błąd. Jeżeli uda mu się powiązać to miejsce z konkretna linią programu źródłowego, w linii tej zostanie umieszczony kursor; w przeciwnym wypadku debugger poinformuje, iż nie jest w stanie zlokalizować odnośnej instrukcji.

Jak wspomniałem wcześniej, jest to narzędzie zaawansowane, którego być może nigdy nie będziesz używał.

Praca krokowa

Praca krokowa jest jedną z najbardziej podstawowych operacji w procesie wykrywania błędów, ale mimo to należy o niej tutaj wspomnieć. Podobnie jak czasami widzi się drzewa, nie widząc lasu, tak też czasami autorzy książek o programowaniu zapominają o przedstawianiu rzeczy oczywistych. Powtarzanie od czasu do czasu wiadomości podstawowych może czasem odsłonić czasem coś, czego przedtem nie wiedziałeś.

Symbole procesu śledzenia umieszczane na gutterze

Przed przystąpieniem do omawiania tematu tej sekcji, opowiem pokrótce o symbolach, które pojawiają się na gutterze w czasie sesji debuggera. W sekcji „Ustawianie i usuwanie punktów przerwań” powiedziałem, że czerwone kropki pojawiają się na gutterze po ustawieniu punktu przerwania w linii kodu; wspomniałem również, że w czasie pracy krokowej zielona strzałka wskazuje bieżący punkt wykonania.

Jedna rzecz, o której dotychczas nie wspominałem, to małe niebieskie kropki pojawiające się na gutterze obok niektórych linii kodu. Kropki te wskazują na linie kodu, które w rzeczywistości zostały przekształcone w kod wynikowy. Na rysunku 10.10 przedstawiony został Edytor Kodu w trakcie zatrzymania debuggera w punkcie przerwania; widoczne są małe kropki wskazujące wygenerowany kod, ikona strzałki wskazująca punkt wykonania, a także ikona punktu przerwania. Znak zaznaczenia na ikonie punktu przerwania oznacza, że sam punkt został sprawdzony i uznany za poprawny.

Przyjrzyj się bliżej rysunkowi 10.10. Zauważ, że małe kropki pojawiają się tylko przy niektórych liniach kodu. Linie bez kropek nie generują żadnego skompilowanego kodu. Weźmy dla przykładu następujące linie:

S : string;

X : Integer;

Dlaczego linie te nie są źródłem kodu? Ponieważ są to deklaracje zmiennych. A co z tą linią:

X:=20;

Rysunek 10.10.

Okno Edytora Kodu z symbolami umieszczonymi na gutterze

0x01 graphic

Dlaczego ta linia nie jest źródłem kodu? Wytłumaczenie zawiera się w jednym słowie: optymalizacja. Kompilator patrzy na dalszą część kodu i nigdzie nie widzi wykorzystania zmiennej X, więc całkowicie ignoruje wszelkie do niej odwołania. W końcu, zwróć uwagę na następujące linie:

{$IFNDEF WIN32}

S:= `Coś jest tutaj nie tak...';

{$ENDIF}

Kompilator nie generuje kodu dla linii zawartej między dyrektywami kompilatora, ponieważ symbol WIN32 jest zdefiniowany w programie Delphi 4. Dyrektywa {$IFNDEF WIN32} jest interpretowana przez kompilator następująco „Skompiluj poniższą linię kodu, jeżeli docelową platformą nie jest 32-bitowa wersja Windows”. Ponieważ Delphi 4 jest kompilatorem 32-bitowym, linia ta nie jest kompilowana (byłaby skompilowana w środowisku Delphi 1, które jest 16-bitowe).

Przekraczanie i wkraczanie

Wracamy do pracy krokowej. Po zatrzymaniu programu w punkcie przerwania, masz wiele możliwości określenia bieżącego stanu kodu. Możesz umieścić zmienne na liście wyrażeń testowych, dokonać inspekcji obiektów przy pomocy Inspektora Śledzenia lub obejrzeć stos wywołań. Masz również możliwość wykonania dalszej części kodu krok po kroku, aby przekonać się co się dzieje ze zmiennymi oraz obiektami w miarę postępowania programu.

W miarę kontynuowania programu krok po kroku, przekonasz się, że linia kodu przeznaczona do wykonania w następnej kolejności jest podświetlona na niebiesko. Jeżeli otwarłeś okno listy wyrażeń testowych i Inspektora Śledzenia, ich zawartość będzie uaktualniana po wykonaniu każdej linii kodu. Jakiekolwiek zmiany zmiennych staną się natychmiast widoczne w oknie listy lub Inspektora.

Debugger środowiska IDE posiada dwie główne komendy pracy krokowej: przekraczanie (Step Over) i wkraczanie (Step Into).

Przekraczanie

Operacja przekraczania (Step Over) oznacza wykonanie kolejnej linii kodu źródłowego i zatrzymanie się na linii po niej następującej. Określenie tej operacji mianem przekraczania jest trochę niepoprawne - mogłoby bowiem sugerować, że dana linia kodu jest „przekraczana” w sensie dosłownym, bez jej wykonywania. Chodzi tu jednak o coś zupełnie innego: otóż, nawet jeżeli dana linia zawiera instrukcję powodującą wywołanie procedury lub funkcji, potraktowana zostaje jako niepodzielna całość - nie nastąpi wejście śledzenia do wnętrza procedury/funkcji.

Najprostszym sposobem wykonania linii kodu źródłowego z trybie Step Over jest naciśnięcie klawisza F8.

Wkraczanie

Wkroczenie (Step Into) pozwala na wejście w kod dowolnej procedury lub funkcji, jaka zostanie napotkana w trakcie pracy krokowej. Komenda wkroczenia, zamiast wykonywać funkcję lub procedurę i przejść do następnej linii (jak miało to miejsce przy przekraczaniu), umieszcza punkt wykonania w pierwszej linii kodu wywołanej funkcji lub procedury. Następnie można przejść przez tę funkcję lub procedurę linia po linii, stosując jedno z poleceń przekraczania lub wkraczania. Skrótem klawiszowym dla polecenia Trace Into jest F7.

0x01 graphic

W miarę krokowego wykonywania programu wszystkie niezbędne moduły źródłowe będą automatycznie ładowane przez Edytor Kodu i wyświetlane na ekranie jeżeli zajdzie taka potrzeba.

Po przeanalizowaniu stanu zmiennych i wykonaniu innych operacji związanych z wykrywaniem usterek, można wznowić wykonanie programu z pełną szybkością klikając na przycisku Run. Program rozpocznie normalną pracę aż do momentu napotkania punktu przerwania.

0x01 graphic

Jeżeli posiadasz Delphi w wersji Professional lub Client/Server, możesz wkraczać do wnętrza kodu VCL. Kiedy napotkasz metodę VCL, polecenie Trace Into przeniesie Cię do kodu źródłowego tej metody. Możesz dokonać inspekcji dowolnych zmiennych. Do pola Search path na stronie Directories/Conditionals opcji projektu musisz w tym celu dodać ścieżkę dostępu do kodu źródłowego VCL. Uaktywnienie tej opcji wymaga zbudowania projektu po dodaniu ścieżki do kodu VCL. Dla większości programistów wkraczanie w kod VCL przynosi znikome korzyści. Jednak osoby doświadczone w programowaniu uznają te cechę za użyteczną.

Wkraczanie do następnej linii

Kolejną, rzadziej stosowaną komendą debuggera, jest wkraczanie do następnej linii (Trace To Next Line - Shift+F7). Prawdopodobnie nie będziesz zbyt często korzystał z tego polecenia, a już szczególnie do czasu, kiedy lepiej zaznajomisz się z zasadami wykrywania błędów i programowaniem dla Windows w ogólności. Niektóre funkcji interfejsu API Windows korzystają z mechanizmu zwanego funkcją zwrotną. Oznacza to, że funkcja Windows wywołuje jedną z Twoich funkcji, aby wykonać określoną czynność.

Jeżeli punkt wykonania wskazuje na funkcję Windows API posiadającą mechanizm zwrotny, polecenie Trace To Next Source Line przemieści punkt wykonania do pierwszej linii funkcji zwrotnej. Efekt działania tego polecenia jest podobny do zwykłego wkraczania, chociaż specyficzna sytuacja, w której stosowane jest to polecenie, jest całkowicie odmienna. Jeżeli nie jesteś w stanie zrozumieć tego o czym mówię, nie przejmuj się. Nie jest to rzecz ważna z punktu widzenia rzeczy, których powinieneś nauczyć się w tym rozdziale.

0x01 graphic

Praca krokowa w ramach procedury/funkcji zwrotnej doprowadzi w końcu do dyrektywy end. Naciśnięcie w tej sytuacji klawisza F8 spowoduje zwrócenie sterowania do Windows, sam zaś program wyjdzie ze stanu zatrzymania. Sytuacja ta wywołuje często dezorientację mniej doświadczonych programistów - z jednej strony program kontynuuje normalną pracę, z drugiej zaś - IDE pozostaje ciągle oknem aktywnym. Uaktywnienie programu (np. za pomocą kombinacji klawiszy Alt+Tab lub kliknięciem w odpowiedni przycisk na pasku stanu Windows) likwiduje tę dwuznaczną sytuację.

Jak wspomniałem wcześniej, praca krokowa jest prostą, lecz intensywnie wykorzystywaną techniką wykrywania błędów - zapamiętaj więc dobrze związane z nią skróty klawiszowe: F7 (Trace Into), F8 (Step Over), F9 (Run).

Śledzenie wewnątrz bibliotek DLL

Zasadniczo testowanie biblioteki DLL podobne jest do testowania pliku wykonywalnego. W kodzie biblioteki umieszcza się punkty przerwań, a debugger po napotkaniu dowolnego z nich przerywa wykonanie programu, identycznie jak w przypadku pliku EXE. Zazwyczaj biblioteka DLL jest testowana przez stworzoną do tego celu aplikację testującą, pracującą pod kontrolą debuggera.

Czasem może się jednak okazać niezbędne przetestowanie biblioteki DLL z plikami wykonywalnymi, które powstały w innych środowiskach programistycznych. Powiedzmy, na przykład, że budujesz bibliotekę DLL, która będzie współpracować z aplikacją stworzoną w środowisku Visual Basic - zdecydowanie nie możesz uruchomić tej ostatniej pod kontrolą debuggera Delphi. Możesz za to wymusić na debuggerze Delphi uruchomienie aplikacji VB jako aplikacji-hosta dla przedmiotowej biblioteki DLL (oczywiście aplikacja-host musi zawierać kod, ładuje bibliotekę i wywołuje z niej żądane podprogramy); aplikację-host specyfikuje się w oknie dialogowym Run Parameters.

W celu wyświetlenia tego okna należy wybrać polecenie menu RunParameters. Nazwę aplikacji-hosta należy wpisać w pole Host Application, a następnie kliknąć na przycisku Load, co spowoduje uruchomienie programu. Wygląd okna Run Parameters przed przystąpieniem do wykrywania błędów w bibliotece DLL przedstawiony został na rysunku 10.11.

Rysunek 10.11.

Określenie aplikacji-hosta w oknie Run Parameters

0x01 graphic

Po uruchomieniu aplikacji-hosta można przystąpić do poszukiwania błędów w bibliotece DLL tak samo, jak robi się to w przypadku zwykłych programów: należy po prostu umieść punkty przerwań w kodzie źródłowym biblioteki.

0x01 graphic

Okno Run Parameters posiada zakładkę Remote; zakładka ta pozwala ustawić parametry dla procesu wykrywania błędów w aplikacji znajdującej się na zdalnym komputerze. Zdalne wykrywanie błędów należy do zaawansowanych zagadnień i nie będzie tutaj omawiane.

Okno dziennika zdarzeń

Dziennik zdarzeń jest specjalnym plikiem Delphi zawierającym komunikaty diagnostyczne - generowane przez Delphi, Twoją aplikację, a niekiedy przez Windows. Przykładowo, w dzienniku tym znajdują się informacje o załadowanych modułach (głównie bibliotekach DLL), czasie uruchomienia i zakończenia aplikacji, napotkaniu punktu przerwania itp. Aby wyświetlić okno dziennika zdarzeń, wybierz polecenie menu ViewDebug WindowsEvent Log. Na rysunku 10.12 przedstawione zostało okno dziennika zdarzeń w trakcie procesu testowania aplikacji.

Okno dziennika zdarzeń posiada własne menu kontekstowe umożliwiające wyczyszczenie dziennika, zapisanie go do pliku tekstowego lub dodanie komentarza. Zapisanie dziennika do pliku ułatwia jego przeglądanie lub poszukiwanie określonego tekstu. Polecenie Properties umożliwia dostosowanie okna dziennika zdarzeń do własnych potrzeb. Okno ustawień dziennika zdarzeń wygląda identycznie jak strona Event Log w oknie Debugger Options (omawiana dalej w sekcji „Strona Event Log”).

Rysunek 10.12.

Okno dziennika zdarzeń

0x01 graphic

Jedna z funkcji Windows API - OutputDebugString (omawiana w sekcji „Funkcja OutputDebugString”) umożliwia wysłanie własnego komunikatu do dziennika zdarzeń.

Okno modułów

Okno modułów (Modules) pokazuje nazwy załadowanych aktualnie modułów, dołączonych do nich plików kodu źródłowego, a także symboli (funkcji, procedur i zmiennych) wyeksportowanych z określonego modułu. W celu otwarcia okna należy wybrać polecenie menu ViewDebug WindowsModules. Okno modułów jest niezwykle zaawansowanym narzędziem, dlatego szczegóły jego działania nie będą tutaj omawiane. Wskazane jest, abyś poświęcił trochę czasu na eksperymenty z tym oknem, aby przekonać się jak ono działa. Rysunek 10.13 przedstawia okno modułów w działaniu.

Rysunek 10.13.

Okno modułów w działaniu

0x01 graphic

Techniki wykrywania błędów

Przy okazji omawiania zagadnień związanych z debuggerem środowiska IDE wspomnianych zostało kilka technik wykrywania błędów. Teraz omówimy jeszcze kilka dodatkowych sposobów, które ułatwią Ci pracę w trakcie poszukiwania błędów.

Funkcja OutputDebugString

Czasami pomocne może okazać się obserwowanie zachowania się programu w trakcie jego pracy. Innym razem chcesz śledzić wartości zmiennych bez potrzeby zatrzymywania programu w punktach przerwań. Właśnie do tego celu służy funkcja OutputDebugString, będąca doskonałym narzędziem w procesie wykrywania błędów, często nie zauważanym przez programistów - głównie ze względu na brak dyskusji na jej temat. Spójrz na ostatni wpis do dziennika zdarzeń na rysunku 10.12; wpis ten powstał na skutek następującego fragmentu kodu:

OutputDebugString(`Wnętrze metody Button1Click...');

Ponieważ Delphi jest zainstalowane jako debugger systemowy, wszelkie łańcuchy generowane przy użyciu funkcji OutputDebugString będą pojawiać się w dzienniku zdarzeń. Wywołania tej funkcji mogą pojawiać się w dowolnych miejscach kodu źródłowego.

Aby wyświetlić wartość zmiennej, wcześniej trzeba przetworzyć ją na łańcuch i przekazać w takiej postaci funkcji OutputDebugString. Przykład:

procedure TForm1.FormCreate(Sender : TObject);

var

X : Integer;

S : string;

begin

{Dowolny fragment kodu...}

S:=Format(`X:= %d', [X]);

OutputDebugString(PChar(S));

end;

Dzięki funkcji OutputDebugString można obserwować zachowanie się programu nawet w przypadku sekcji kodu silnie uwarunkowanych czasowo.

Śledzenie błędów naruszenia ochrony dostępu

Błąd naruszenia ochrony dostępu jest generowany przez Windows, gdy program próbuje zapisać coś do fragmentu pamięci, który do niego nie należy. Błędy tego typu napotykają wszyscy programiści w trakcie prac nad aplikacjami Windows.

Błędy naruszenia ochrony dostępu są trudne do wyśledzenia, zarówno dla początkujących, jak i zaawansowanych programistów Windows. W miarę rozwijania swoich umiejętności, wielu programistów zyskuje szósty zmysł wykrywania przyczyny powstawania tego typu błędów. Poniżej znaleźć można kilka wskazówek, którymi należy kierować się przy wyszukiwaniu nieuchwytnych błędów naruszenia ochrony. Nie są to jedyne sytuacje powodujące w efekcie załamanie się programu, ale ich występowanie jest najczęstsze.

Niezainicjowane wskaźniki

Niezainicjowany wskaźnik to zadeklarowany wskaźnik, który posiadając przypadkową wartość nie wskazuje żadnego konkretnego obiektu w programie (mówiąc inaczej - wskazuje na losowy fragment danych.) W najlepszym przypadku wskaźnik taki pokazuje na nieużywany fragment pamięci. W najgorszym przypadku niezainicjowany wskaźnik wskazuje na fragment pamięci gdzieś w Twoim programie. Sytuacja taka może prowadzić do niekonsekwentnego zachowania się programu przy każdym jego uruchomieniu. Przed pierwszym użyciem wskaźnika, a także po usunięciu obiektu na który wskazuje, przypisuj mu wartość nil. Próba odwołania się do wskaźnika zawierającego wartość nil spowoduje zatrzymanie programu z błędem naruszenia ochrony, jednak tym razem linia kodu źródłowego, która wywołała błąd, zostanie podświetlona przez debugger umożliwiając szybką identyfikację problemowego wskaźnika.

Usuwanie nieistniejących już wskaźników

Usunięcie wskaźnika, który został usunięty już wcześniej, owocuje błędem naruszenia ochrony.

Zastosowanie znajduje tutaj rada udzielona w przypadku niezainicjowanych wskaźników: ustawiaj usunięte wskaźniki na wartość nil. Usunięcie wskaźnika zawierającego nil jest całkowicie bezpieczne. Dlatego ustawianie wskaźnika, po usunięciu obiektu na który wskazuje, na wartość nil będzie gwarancją uniknięcia niepożądanych efektów w sytuacji przypadkowego usunięcie wskaźnika po raz drugi.

Przekraczanie zakresu tablicy

Zapis danych poza przydzielony rozmiar pamięci (w szczególności - tablicy) może spowodować błąd naruszenia ochrony. W pewnych sytuacjach poza dostępną pamięć nie spowoduje żadnych efektów ubocznych, program będzie pracował poprawnie, jednak po pewnym czasie „wysypie się”. Kiedy zajdzie taka sytuacja, będziesz szukał błędu w miejscu, w którym program przerwał pracę, podczas gdy rzeczywisty problem wystąpił w całkowicie innej części kodu. W innym przypadku zapis poza dostępny obszar pamięci z miejsca okaże się nieprawidłowy i program natychmiast przerwie pracę. W ekstremalnych sytuacjach może dojść nawet do załamania systemu Windows.

Przekraczanie rozmiaru tablicy można nieco zminimalizować stosując sprawdzanie rozmiaru (opcja kompilacji $R). Przy włączonej opcji sprawdzania zakresu (domyślnie), przy każdej operacji na tablicy kompilator będzie sprawdzał, czy przetwarzany element mieści się w jej zakresie. Dla przykładu, poniższy fragment kodu spowoduje błąd kompilatora:

procedure TForm1.ButtonClick1(Sender : TObject);

var

A : array[0..20] of Char;

begin

A[30] := `a';

end;

W powyższym kodzie następuje próba dostępu do 30 elementu tablicy, która składa się tylko z 21 elementów. Kompilator zauważa, że dostęp do tablicy następuje poza jej zadeklarowanym zakresem i generuje błąd. Sprawdzanie zakresu nie działa jednak w przypadku zmiennych. Następny fragment kodu nie spowoduje powstania błędu kompilatora:

procedure TForm1.ButtonClick1(Sender : TObject);

var

X : Integer;

A : array[0..20] of Char;

begin

X := 30;

A[X] := `a';

end;

Mimo że zakres tablicy zostaje przekroczony o dziewięć bajtów, nie pojawia się żaden błąd kompilatora, ponieważ w chwili kompilacji wartość X nie jest znana kompilatorowi. Jeżeli jednak powyższy kod kompilowany był z opcją {$R+}, wystąpi błąd wykonania, gdyż kompilator wygenerował kod sprawdzający legalność odwołań.

Naruszenie ochrony dostępu przy wyjściu z programu

Jeżeli program zostaje zatrzymany z błędem naruszenia ochrony dostępu podczas normalnego kończenia swojej pracy, jest to zwykle oznaka zbyt małego rozmiaru stosu. Chociaż jest to sytuacja nietypowa dla programu 32-bitowego, może jednak zaistnieć w przypadku warunków ekstremalnych. Przyczyną błędu pogwałcenia dostępu podczas wyjścia może być również próba usunięcia nieistniejącego już wskaźnika, o czym była mowa wcześniej.

Krótkie wskazówki do procesu wykrywania błędów

Oprócz wszystkich, przedstawionych do tej pory wskazówek, warte zastosowania mogą okazać również poniższe:

0x01 graphic

Jeżeli pracujesz w Delphi pod kontrolą Windows 95, korzystaj oszczędnie z polecenia Program Reset. W niektórych przypadkach, użycie tego polecenia do przerwania pracy aplikacji może spowodować załamanie się Windows 95. Ponieważ nie wszystkie systemy Windows 95 zachowują się w ten sposób, być może w ogóle nie będziesz miał do czynienia z tym problemem. Windows NT cierpi z tego powodu nieporównywalnie mniej niż Windows 95, więc w przypadku tej platformy możesz dowolnie często korzystać z polecenia Program Reset. Osobiście, korzystam z tego polecenia tylko w przypadku gdy aplikacja zablokuje się podczas sesji debuggera.

Jedną z najlepszych rad, jaką mogę Ci przekazać w sprawie wykrywania błędów, jest korzystanie z programów sprawdzających pamięć, takich jak np. Memory Sleuth firmy TurboPower Software. Być może otrzymałeś ten program jako część Delphi 4 (był on dołączany za darmo do Delphi przez ograniczony okres czasu). Memory Sleuth sprawdza pamięć na okoliczność systematycznego jej gubienia. Program tego typu może zaoszczędzić wiele czasu podczas testowania aplikacji. Jeżeli aplikacja powoduje gubienie pamięci, stanie się ona z pewnością źródłem problemów dla swych użytkowników. Kiedy użytkownicy miewają problemy, miewasz je również Ty. Wykrywając i pozbywając się tego typu usterek odpowiednio wcześnie, zaoszczędzisz sobie czasu, a Twoim użytkownikom frustracji.

Jeżeli nie otrzymałeś programu Memory Sleuth razem z Delphi 4, możesz zamówić go na stronie internetowej firmy TurboPower (www.turbopower.com). Innym programem wykrywającym gubienie pamięci jest BoundsChcecker firmy NuMega Technologies.

Opcje debuggera

0x08 graphic
Opcje debuggera mogą być ustawiane na dwóch poziomach: projektu i środowiska. Opcje na poziomie projektu były omawiane w poprzednim rozdziale, w sekcjach „Strona Compiler” i „Strona Linker”. Opcje ustawiane na poziomie globalnym znajdują się w oknie ustawień debuggera (Debugger Options). W celu wywołania tego okna należy wybrać polecenie menu ToolsDebugger Options.

U dołu tego okna znajduje się pole wyboru Integrated debugging. Opcja ta decyduje o tym, czy do wykrywania błędów stosowany będzie debugger środowiska IDE. Przy wyłączonej opcji Integrated debugging zintegrowany debugger nie jest używany. Oznacza to, że po kliknięciu przycisku Run program zostanie uruchomiony, ale, ze względu na nieaktywność debuggera, nie będą funkcjonować żadne punkty przerwań.

Okno opcji debuggera składa się z czterech zakładek: General, Event Log, Language Exceptions i OS Exceptions. Każda z tych zakładek jest omawiana w dalszych sekcjach.

Strona General

Strona General (rysunek 10.14) umożliwia modyfikację ogólnych ustawień debuggera.

Rysunek 10.14.

Strona General w oknie opcji debuggera

0x01 graphic

Opcja Map TD32 keystrokes on run nakazuje Edytorowi Kodu stosowanie trybu mapowania naciśniętych klawiszy zgodnego z zewnętrznym Turbo Debuggerem firmy Borland. Cecha ta ma szczególne znaczenie dla osób, które spędziły dużo czasu z tym programem są przyzwyczajone do stosowanych w nim kombinacji klawiszy.

Opcja Make buffers read-only on run ustawia bufory Edytora Kodu w stan „tylko do odczytu” w trakcie pracy programu pod kontrolą debuggera. W wyniku tego po rozpoczęciu sesji debuggera nie ma możliwości edycji kodu źródłowego, aż do momentu zakończenia programu. Ja zawsze wyłączam tę opcję, ponieważ, w trakcie poszukiwania błędów, regularnie dokonuję modyfikacji w kodzie.

Pole wyboru Inspectors stay on top określa, czy okna Inspektora Śledzenia powinny znajdować się na wierzchu względem Edytora Kodu. Jest dobra cecha, ponieważ podczas pracy krokowej będziesz prawdopodobnie chciał mieć wszystkie okna Inspektora na wierzchu.

Opcja Rearrange editor local menu on run powoduje zmianę organizacji menu kontekstowego Edytora Kodu podczas pracy programu pod kontrolą debuggera. Kiedy opcja ta jest aktywna, polecenia menu kontekstowego specyficzne dla procesu wykrywania błędów są przesuwane na szczyt tego menu, dzięki czemu łatwiej można je znaleźć.

Strona Event Log

Strona Event Log umożliwia modyfikację opcji dziennika zdarzeń. Możesz określić maksymalną liczbę komunikatów, jaka ma prawo pojawić się jednocześnie w dzienniku zdarzeń lub pozostawić tę liczbę na poziomie nieograniczonym. Dozwolone jest również określenie rodzaju komunikatów, jakie powinny pojawiać się w dzienniku.

Strona Language Exceptions

Strona Language Exceptions pozwala określić typy wyjątków VCL, które powinny być wyłapywane przez debugger (wyjątki omawiane są w rozdziale 14. „Programowanie zaawansowane”). Najważniejszym elementem tej strony jest opcja Stop on Delphi Exceptions. Jej włączenie powoduje zatrzymywanie programu przez debugger, kiedy wygenerowany zostanie wyjątek. Kiedy opcja ta jest wyłączona, wyjątek VCL jest obsługiwany w zwykły sposób - przez okno dialogowe informujące użytkownika o niepoprawnej sytuacji jaka wystąpiła w programie.

0x01 graphic

Przy włączonej opcji Stop on Delphi Exceptions debugger zatrzymuje wykonanie programu po wystąpieniu wyjątku nawet wtedy, gdy jest on obsługiwany przez Twój program. Jeżeli nie chcesz, aby debugger zatrzymywał się przy każdym wyjątku, wyłącz tę opcję. Opcja ta stanowi następstwo opcji Break on exception stosowanej w poprzednich wersjach Delphi.

Opcja Exception Types to Ignore określa typy wyjątków, które mają być ignorowane przez debugger. Wszelkie klasy wyjątków umieszczone na tej liście będą pomijane przez debugger i obsługiwane w sposób standardowy. Opcja ta faktycznie jest równoważna wyłączeniu opcji Stop on Delphi Exceptions dla wybranych typów wyjątków.

Aby dodać typ wyjątku do listy, wystarczy kliknąć na przycisku Add i wpisać nazwę klasy wyjątków. Na przykład, aby nakazać debuggerowi ignorowanie wyjątków dzielenia przez zero, należy kliknąć na przycisku Add, a następnie wpisać wartość EDivByZero w pole Exception Type. Proces ten został przedstawiony na rysunku 10.15.

Typ wyjątku dodany do tej listy będzie obowiązywał dla wszystkich projektów (również tych nowo tworzonych).

Strona OS Exceptions

Strona OS Exceptions (rysunek 10.16) określa, czy wyjątki systemu operacyjnego są obsługiwane przez debugger, czy też przez program użytkownika.

Jeżeli w sekcji Handled By ustawiona jest opcja User Program, program jest zatrzymywany przez debuggera w chwili wystąpienia wyjątku. W przypadku aktywnej opcji Debugger wyjątek VCL jest obsługiwany w standardowy sposób - przez wyświetlenie okna informacyjnego z komunikatem informującym użytkownika o nieprawidłowości, jaka wystąpiła w programie.

Rysunek 10.15.

Dodawanie elementu do listy wyjątków ignorowanych przez debugger

0x01 graphic

Rysunek 10.16.

Strona OS Exceptions

0x01 graphic

0x01 graphic

Przy aktywnej opcji Debugger (w sekcji Handled By), debugger zatrzymuje się przy wystąpieniu wyjątku, nawet jeżeli wyjątek ten jest obsługiwany przez Twój program. Jeżeli nie chcesz, aby program zatrzymywany był przy każdym wystąpieniu wyjątku, uaktywnij pole opcji User Program. Opcja ta zastąpiła spotykaną w poprzednich wersjach opcję Break on exception.

Opcja On Resume określa sposób potraktowania wyjątku po wznowieniu wykonania programu, poprzedzonego wystąpieniem tego wyjątku.

Lista Exception zawiera listę możliwych wyjątków systemu operacyjnego. Aby ustawić opcję dla określonego wyjątku, trzeba wybrać go na liście, a następnie dokonać odpowiednich ustawień w sekcjach Handled By i On Resume. Ikony na prawym marginesie listy wskazują wybrane opcje obsługi i wznawiania.

Podsumowanie

Wykrywanie błędów to nigdy nie kończące się zadanie. Termin „debugging” oznacza znacznie więcej niż tylko śledzenie błędów w programie. Zmyślni programiści uczą się stosować debugger już od samego początku nowego projektu. Można powiedzieć, że debugger w równym stopniu służy wykrywaniu błędów i rozwijaniu oprogramowania. Po przestudiowaniu tego rozdziału powinieneś przyswoić sobie podstawy użytkowania debuggera. Oprócz tego będziesz musiał jeszcze spędzić trochę czasu na rzeczywistej pracy z debuggerem, aby nabrać wprawy. Niemniej jednak teraz wiesz już od czego zacząć.

Warsztat

Warsztat składa się z pytań kontrolnych oraz ćwiczeń utrwalających i pogłębiających zdobytą wiedzę. Odpowiedzi do pytań możesz znaleźć w dodatku A.

Pytania i odpowiedzi

Więcej niż prawdopodobne jest to, że albo ustawiłeś w swoim programie dużą ilość punktów przerwań, do których następnie wyłączyłeś dostęp i zapomniałeś o nich lub ustawiłeś w swoim kodzie przynajmniej jeden warunkowy punkt przer­wania. Przejdź do listy punktów przerwań i usuń punkty, z których nie korzystasz w danej chwili. Zwróć również uwagę na liczbę zmiennych umieszczonych na liście wyrażeń testowych (Watch List).

Tak. Najpierw dodaj zmienną do listy wyrażeń testowych. Następnie kliknij na niej dwukrotnie. Kiedy otwarte zostanie okno dialogowe właściwości podglądu (Watch Properties) wybierz opcję Decimal. Teraz ponownie dodaj tę samą zmienną do okna listy wyrażeń testowych, ale tym razem wybierz opcję Hexadecimal. Oba elementy znajdą się na liście wyrażeń testowych, jedna w formacie dziesiętnym, druga w szesnastkowym.

Oczywiście. Wpisz wyrażenie warunkowe w pole Conditional okna właściwości punktu przerwania, a liczbę przejść przez ten punkt - w pole Pass Count. Kiedy spełniony zostanie warunek punktu przerwania i osiągnięta zostanie odpowiednia liczba przejść, program zostanie zatrzymany.

Kiedy punkt wykonania znajdzie się w linii, w której wywoływana jest funkcja, zamiast klawisza F8 naciśnij klawisz F7 (Trace Into). Teraz możesz krok po kroku możesz wykonać całą funkcję.

Mówiąc krótko, chodzi o optymalizację. Kompilator optymalizuje określone sekcje kodu, a przez to uniemożliwia przeglądanie pewnych zmiennych. W istocie zmienne, z punktu widzenia debuggera, nie istnieją w tym czasie. Aby uniknąć tego problemu można wyłączyć optymalizację (strona Compiler w oknie opcji projektu). Pamiętaj o ponownym włączeniu optymalizacji przed ostateczną kompilacją aplikacji.

Ponieważ po powrocie z danej metody, program nie ma nic więcej do wykonania, więc przechodzi do stanu jałowego. W rzeczywistości w punkcie, do którego dotarł program, nie ma więcej kodu do wykonania, więc debugger zwraca sterowanie do programu, który go wywołał.

Wybierz polecenie menu ViewDebug WindowsCPU. Samo otwarcie okna jednak nie wystarczy, trzeba jeszcze wiedzieć jak z niego korzystać, a to już całkiem inna sprawa!

Quiz

  1. W jaki sposób umieszcza się punkt przerwania w linii kodu?

  1. Co to jest nieprawidłowy punkt przerwania?

  2. W jaki sposób ustawia się warunkowy punkt przerwania?

  3. W jaki sposób można zmienić właściwości elementu występującego na liście wyrażeń testowych?

  4. Podaj najszybszy sposób na dodanie zmiennej do listy wyrażeń testowych.

  5. Jakiego narzędzia należy użyć do przejrzenia pól i metod klasy?

  6. Jak wejść do wnętrza metody w trakcie pracy krokowej?

  7. W jaki sposób można zmodyfikować wartość zmiennej w czasie pracy programu?

  8. Co umożliwia wysyłanie własnych komunikatów do dziennika zdarzeń (Event Log)?

  9. Do czego służy opcja Integrated Debugging, usytuowana w dolnej części okna opcji debuggera?

Ćwiczenia

  1. Załaduj program ScratchPad, który stworzyłeś w rozdziale szóstym „Praca z Projektantem Formularzy i Edytorem Menu”. Umieść punkty przerwań w metodach FileOpenClick i FileSaveClick. Uruchom program. Kiedy wykonanie programu zostanie wstrzymane dokonaj inspekcji klas OpenDialog i SaveDialog.

  1. Kontynuując ćwiczenie pierwsze, po zatrzymaniu programu w punkcie przerwania przejdź do pracy krokowej i przeanalizuj operacje jakie kolejno wykonuje program.

  2. Załaduj program DebugTst stworzony wcześniej w tym rozdziale. Umieść punkt przerwania we wnętrzu metody WatchBtnClick. Dodaj zmienne S i X do listy wyrażeń testowych, każdą z nich po cztery razy. Edytuj każdy z podglądanych elementów zmieniając jego opcje wyświetlania. Uruchom program, a następnie wykonaj krokowo metodę obserwując zmiany zachodzące w liście wyrażeń testowych.

  3. Do metody wspomnianej w ćwiczeniu trzecim dodaj warunkowy punkt przerwania. Umieść go bezpośrednio za linią kodu X:=Width;. Jako warunku użyj wyrażenia X = 0, a następnie uruchom program. Co się stanie z programem? (Nic, ponieważ zmienna X nigdy nie osiąga wartości zero.)

  4. Kontynuując ćwiczenie czwarte, edytuj warunkowy punkt przerwania i ustaw nowy warunek postaci X > 400. Uruchom program. Zmień rozmiar okna, po czym kliknij na przycisku Test Podglądu. Powtórz te czynności kilkakrotnie, zmieniając za każdym razem rozmiar okna. Co się dzieje? (Tym razem debugger zatrzymuje się w punkcie przerwania, kiedy szerokość okna przekracza 400 pikseli.)

  5. Załaduj dowolny program i przejdź do Edytora Kodu. Umieść kursor w dowolnej linii kodu i wybierz polecenie menu kontekstowego - Run to Cursor. Poeksperymentuj z programem, aż napotkany zostanie punkt przerwania.

  6. Załaduj ponownie program DebugTst. Umieść punkt przerwania we wnętrzu metody WatchBtnClick i uruchom program. Kiedy program zatrzyma się w punkcie przerwania, użyj Inspektora Śledzenia do przejrzenia struktury WatchBtn.

442 Część II

442 C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\10.doc

C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\10.doc 441

Rozdzia³ 10 Wykrywanie błędów w aplikacjach 441



Wyszukiwarka

Podobne podstrony:
Profi 320 10 100 1 zbiornik 01
Profi 320 10 100 3 zbiornik 01
Profi 255 10 50 Profi 255 10 100 glowka 01
Profi 255 10 100 zbiornik 01
odejmowanie 100 10
dodawanie 100 10
Zasilanie bunkra 80-100 dni - 10, elektroda
ustawa o własności lokali, ART 7 WłasLok, III CZP 100/10 - z dnia 9 grudnia 2010 r
mnozenie do 100 10
10 2005 100 102
odejmowanie 100 10
Owners Manual ES 10, 20, 30, 80, 90, 100, 25C (Polish)
Ilg, Ames, Baker Rozwój psychoczny dziecka od 0 do 10 lat str 68 100
Starowicz Z L Przemoc seksualna str 10 30, 48 51, 69 70, 100 101

więcej podobnych podstron