Umíme to s Delphi: 148. díl – zastavujeme, ladíme a odvšivujeme

Dnešní článek se po delší době věnované konkrétní technologii zaměří na integrované prostředí Delphi. Často se nám stane, že vytvářený program neběží tak, jak jsme si představovali: vyhazuje výjimky, hlásí různé chyby a padá. V tom okamžiku nastupuje práce pro integrovaný debugger Delphi, přičemž jako jednu z prvních funkcí obvykle využijeme tzv. body přerušení – breakpointy.

Dnešní díl seriálu se zaměřuje na zajímavý rys integrovaného prostředí Delphi. Prostředí Delphi a jeho možnostem jsme se v seriálu už poměrně dlouho vyhýbali, což je svým způsobem škoda, protože nám může v mnoha případech usnadnit práci.

Dnes se podíváme na poměrně zhusta používanou možnost, kterou prostředí Delphi nabízí. Jedná se o body přerušení – breakpointy, které umožňují pozastavit provádějí programu v místě, kde je prokazatelně a očividně “cosi špatně”.

Breakpointy – co to je?

Předpokládám, že většina čtenářů se s pojmem „breakpoint“ již setkala a zhruba ví, jak breakpointy (neboli tzv. body přerušení) fungují. Kromě toho, otázkou breakpointů jsme se už v našem seriálu velmi povrchně zabývali, a to v jeho 5. části, a dále v dílech 56, 115 a 116.

Pojďme se nejprve podívat na to, jak jsou breakpointy definovány samotnou firmou Borland:

„Breakpoint je místo v programu, které jsme předem označili a kde chceme pozastavit vykonávání programu v rámci jeho ladění. Jakmile byl program pozastaven, můžeme dále zkoumat jeho stav v daném bodě provádění. Stav programu přitom zahrnuje hodnoty všech proměnných přístupných z bodu provádění, dále hodnotu datových struktur a funkce a procedury umístěné v zásobníku volání.“

Existuje několik druhů breakpointů; používáme vždy ten, který se hodí pro daný účel, avšak v největší míře bývá využíván Source Breakpoint, tedy breakpoint umístěný na určitou řádku ve zdrojovém kódu našeho programu. Kromě Source Breakpointů existují ještě Data Breakpoints, Address Breakpoints a Module Load Breakpoints.

  • Source Breakpoint si lze představit jako řádku ve zdrojovém kódu (source code), kterou označíme. Když pak spustíme aplikaci v integrovaném prostřední Delphi, provádění programu se zastaví v okamžiku, kdy dojde řada na tuto označenou řádku. Poté, co je provádění programu zastaveno, můžeme ručně vykonávat celou řadu „ladicích“ činností, například prohlížet nebo měnit hodnoty proměnných, prohlížet stav paměti, krokovat program po následujících příkazech apod.
  • Data Breakpoint má podobnou funkci (také způsobí provádění aplikace spuštěné v integrovaném debuggeru), pouze zastavovací podmínka je trochu jiná: neoznačujeme řádku ve zdrojovém kódu, ale označíme určité paměťové místo a požádáme o pozastavení provádění v okamžiku, kdy se hodnota daného paměťového místa změní.
  • Module Load Breakpoint – tento breakpoint slouží pro pozastavení běhu programu v okamžiku, kdy je do paměti načten příslušný modul, typicky DLL knihovna nebo BPL balíček.
  • Konečně Address Breakpoint – jedná se opět o mechanismus umožňující pozastavit běžící program, nicméně neoznačujeme ani řádku ve zdrojáku ani paměťové místo, nýbrž paměťovou adresu obsahující instrukci, jejíž provedení způsobí pozastavení běhu.

Z těchto tří druhů breakpointů se ponejvíce používá nejjednodušší verze – Source Breakpoint. Pokud je nějaká část programu problematická, jednoduše nastavíme na jeden nebo více řádků v této problematické oblasti breakpointy, následně v Delphi spustíme program (F9) a když dojde na breakpoint (a když se tedy program zastaví), začneme hledat, co je špatně.

Breakpointy umožňují celou řadu dalších rozšíření, jako nastavování podmínek, za nichž se má program zastavit a za nichž ne (například když hodnota určité proměnné překročí určitou hodnotu apod.), nebo jako nastavování počtu průchodů, které mají proběhnout nerušeně (např. když chceme zastavit provádění bloku příkazů až poté, co bude proveden podesáté apod.).

Obecně vzato, breakpointy představují silný a účinný nástroj, který používají skoro všichni programátoři v okamžiku, kdy se dostanou do úzkých a kdy jejich vyvíjená aplikace neběží tak, jak by měla a jak oni si představují.

Nastavení breakpointu

Nastavení breakpointu se provede několika různými způsoby, záleží také na tom, který z breakpointů nastavujeme.

Source Breakpoint

Pro nastavení Source Breakpointu najedeme textovým kurzorem na požadovanou řádku ve zdrojovém kódu. Následně máme několik možností, jak breakpoint nastavit:

  • klepneme myší na levý okraj okna vedle požadované řádky,
  • klepneme pravým tlačítkem myši kdekoliv v požadované řádce, zvolíme Debug – Toggle Breakpoint,
  • najedeme textovým kurzorem kamkoliv na požadovanou řádku a stiskneme klávesu F5 (při standardním mapování kláves),
  • zvolíme v hlavní nabídce Delphi položku Run – Add Breakpoint – Source Breakpoint,
  • klepneme pravým tlačítkem v okně Breakpoint List a zvolíme Add – Source Breakpoint.

Existují i další možnosti, například zadat číslo řádku do dialogu Add Breakpoint apod.

V každém případě se objeví červený znak a zvolená řádka se také podbarví červeně, viz obrázek:

Dodejme, že Source breakpoint nelze nastavit na všechny řádky zdrojového kódu; pokusíme-li se vložit breakpoint např. řádku s komentářem, deklarací proměnné apod., nebudeme úspěšní.

Address Breakpoint

Pro nastavení Address Breakpointu zvolíme Run – Add Breakpoint – Address Breakpoint a v následném dialogovém okně zadáme požadovanou adresu.

Důležité je říct, že Address breakpoint lze z pochopitelného důvodu nastavit jen v případě, že příslušná aplikace běží a je typicky pozastavena v integrovaném debuggeru. Proč?

Předtím, než je aplikace spuštěna (a tedy natažena do paměti) nemá smysl odkazovat na jakékoliv paměťové adresy, protože až při fyzickém zavedení aplikace do paměti se rozhodne o konkrétních paměťových adresách.

Breakpoint lze nastavit také z okna CPU window, tedy z okna zobrazujícího stav a obsah procesoru a jeho registrů. Toto okno lze otevřít volbou View – Debug Windows – CPU z hlavní nabídky Delphi. Okno však může být zobrazeno také až poté, co se aplikace rozběhne v paměti, nikoliv v době sepisování zdrojového kódu. Důvod je stejný jako v předchozím případě: dokud není aplikace zavedena a fyzicky spuštěna, nelze dost dobře zjistit, co způsobí její spuštění uvnitř procesoru.

Poté, co se nám podaří pozastavit běh aplikace (a toho lze dosáhnout opět řadou způsobů, například nastavením Source Breakpointu, použitím volby Run – Run to cursor, pozastavením běhu volbou Run – Program Pause apod.), se otevře dialogové okno (viz obrázek), v němž musíme typicky zadat alespoň paměťovou adresu, případně další podmínky zastavení.

Data Breakpoint

Pro nastavení Data Breakpointu zvolíme Run – Add Breakpoint – Data Breakpoint. Pro tento druh breakpointu platí v zásadě totéž, co v předchozím případě: breakpoint ze nastavit pouze při spuštěné (a tedy typicky pozastavené) aplikaci uvnitř prostředí Delphi.

Parametry breakpointu

Jak jsme už zmínili, nastavení breakpointu neznamená vždycky jen „zastavení v okamžiku, kdy se dojde k němu při provádění programu dojde“. Breakpointy jsou mnohem sofistikovanější a umožňují nastavovat celou řadu dalších věcí, které modifikují jejich chování a vlastnosti.

Otevřete si například dialog pro přidání Breakpointu (Run – Add Breakpoint – Source Breakpoint). Uvidíte, že body přerušení umožňují nastavit několik parametrů: Condition, Pass Count a Group.

  • Condition – v této sekci lze nastavit podmínku, při níž má breakpoint „zabrat“. Pokud do pole Condition například zapíšeme výraz „X = 10“, způsobí daný breakpoint zastavení programu pouze v případě, že hodnota proměnné X bude v okamžiku, kdy se dojde k breakpointu, rovna číslu 10. V ostatních případech breakpoint nezabere a nic nezastaví.
  • Pass Count – počet průchodů. Někdy bychom si přáli, aby byl breakpoint po dobu několika průchodů ignorován, a až po dosažení určitého počtu aby zabral. Chceme-li třeba, aby breakpoint zastavil až poslední z deseti průchodů cyklem, zapíšeme do Pass Count hodnotu 10.
  • Group – breakpointy mohou být sdružovány do skupin a následně hromadně aktivovány-deaktivovány. Stačí zadat jakékoliv jméno skupiny do editačního pole Group. Práci se skupinami breakpointů se budeme věnovat příště.

Pokud ve zmíněném dialogovém okně klepnete na tlačítko Advanced, budete moci zadat i několik dalších parametrů bodu přerušení. Těmito pokročilými parametry se budeme zabývat za týden.

Zjišťujeme informace o breakpointech

Delphi poskytuje integrovaného správce breakpointů, tedy okno, v němž lze souhrnně zjišťovat informace o všech breakpointech, v němž je lze vypínat a zapínat, a v němž lze přidávat nové body přerušení.

Toto okno se jmenuje Breakpoint List a otevřít jej můžeme po zvolení Run – Debug Windows – Breakpoints. Po otevření okna se otevře seznam všech definovaných bodů přerušení, v němž lze s jednotlivými breakpointy provádět celou řadu činností, počínaje jejich zapínáním-vypínáním, přes nastavování podmínek apod.

Znovuspuštění pozastaveného programu

Říkáme-li, že breakpoint způsobí pozastavení programu a umožní prozkoumat tav programu a všechny podezřelé hodnoty, měli bychom také říci, jak následné v provádění programu pokračovat (ať už natrvalo, a nebo jen do příštího breakpointu).

Provede se to jednoduše – stejně, jako když chceme program poprvé spustit. Stačí klepnout na tlačítko F9 nebo zvolit z hlavní nabídky Delphi položku Run – Run.

Příklad použití breakpointů

Závěrem vytvoříme velmi jednoduchý příklad, na němž si předvedeme použití několika breakpointů.

Vytvořte v Delphi novou aplikaci, na formulář vložte jediné tlačítko (Button1) a ošetřete jeho událost OnClick. Obsluha události OnClick bude zcela primitivní a bezúčelná: bude pouze simulovat nějakou programovou činnost, v níž mohou vzniknout potencionální problémy, které se chystáme odladit. Obsluha je zde:

procedure TForm1.Button1Click(Sender: TObject);
const
  MAX = 1000;
  POCET = 10;

var
  I, J, K: Integer;

begin
  for I := 1 to POCET do begin
    J := Random(MAX);
    K := Random(Max);

    ListBox1.Items.Add(IntToStr(J + K));
  end;

end;

Nyní si zkusíme použití několika breakpointů. Začneme jednoduchým source breakpointem.

Source Breakpoint

Source Breakpoint vložíme například klepnutím myší na okraj Code Editoru v řádce nastavující náhodné číslo do proměnné K (viz obrázek):

Když nyní program spustíme a klepneme na tlačítko Button1, dojde k zastavení programu v příslušné řádce.

Nyní (stále za zastaveného programu) můžeme třeba prohlédnou hodnoty proměnných. Zvolíme tedy Run – Add Watch v hlavní nabídce Delphi a v otevřeném dialogovém okně zadáme do pole Expression písmeno J. Otevře se další okno, v němž si můžeme prohlížet aktuální obsah proměnné J.

Dále se třeba podíváme na obsah lokálních proměnných. Zvolíme z hlavní nabídky položku View – Debug Windows – Local Variables. Otevře se okno informující nás o všech lokálních proměnných a jejich aktuálních hodnotách, viz obrázek:

Další možnost spočívá v prohlédnutí aktuálního obsahu procesoru. Zvolíme tedy View – Debug Windows – CPU. Otevře se okno s informace o všem důležitém, co je v daném okamžiku k nalezení v procesoru, přesněji řečeno v registrech procesoru a dále v paměti, viz obrázek:

U tohoto okna si také předvedeme použití Address Breakpointu.

Address Breakpoint

Dejme tomu, že budeme chtít nastavit breakpoint tak, aby došlo k zastavení programu v okamžiku, kdy se provede instrukce na paměťové adrese $00450cc0.

Zvolíme tedy (stále za zastaveného programu z předchozí podkapitoly) Run – Add Breakpoint – Address Breakpoint. V otevřeném dialogu musíme zadat adresu, v našem případě se zaměříme na adresu $00450cc0. Zadáme tedy tuto adresu a klepneme na OK. Následně můžeme znovuspustit provádění programu (F9); při dosažení dané adresy bude program znovu pozastaven.

Data Breakpoint

Nastavení Data Breakpointu se provádí stejně jako nastavení address Breakpointu, což jsme si ukázali v předchozím odstavci. Data Breakpoint umožňuje zadat paměťovou adresu, na níž jsou umístěna data. Pokud se data na dané adrese změní, dojde k zastavení programu.

Používání Data Breakpointů se hodí zejména v případě, kdy začneme při programování zhusta využívat dynamické datové struktury, ukazatele, dynamické přidělování paměti apod. Potom často nemáme mnoho jiných možností, jak se dobrat k podstatě problému, než se zaměřit na dění v paměti, protože pokud se nám podaří vyrobit chybu typu „zbloudilý ukazatel“ (zapisujeme cosi na adresu, kam cosi ukazuje, bohužel však ono cosi ukazuje jinam, než by mělo), je nesmírně těžké takovou chybu odchytit.

Ukazateli a vůbec dynamickou pamětí se budeme v našem seriálu brzy zabývat podrobněji.

Na tomto místě tedy stačí poznamenat, že takovýmto způsobem pracujeme s breakpointy, a to nejen s těmi tradičními (Source), ale i se sofistikovanějšími, tedy Address nebo Data.

Závěrem

Dnešní článek byl vlastně jen úvodem k tomu, co nás čeká příště. To si totiž povíme o zajímavé vlastnosti breakpointů, která není povšechně příliš známá, která však v některých případech může být velmi cenná.

Dnes víc neprozradím, snad jen naznačím: breakpoint, a to i ten aktivní, zapnutý a správně nakonfigurovaný, nemusí vůbec způsobit zastavení běhu programu. To je ale vše, podobnosti se dočtete za týden.

Diskuze (4) Další článek: Firefox obsahuje dvě extrémně kritické chyby

Témata článku: , , , , , , , , , , , , , , , , , , , , , ,