Ladění aplikace
Ukážeme si základní použití debuggeru gdb - jak probíhá ladění aplikace lokálně, tzn. když debugger i laděná aplikace běží na stejném počítači. Seznámíme se s ovládáním debuggeru gdb a naučíme se pracovat s jeho nejpoužívanějšími příkazy.
Ladím, tedy jsem
Jak to tedy funguje? Při spuštění debuggeru gdb zadáváme jako parametr název aplikace, kterou chceme ladit. Gdb se spustí a jako podřízený proces vytvoří naši aplikaci (viz obrázek 1.3). Od toho okamžiku gdb řídí běh aplikace. Pomocí příkazů zadávaných do konzole gdb můžeme aplikaci ladit, tj. kontrolovat její běh, vkládat breakpointy, prohlížet si zdrojový kód okolo aktuální pozice, zjišťovat hodnoty proměnných atd.
Úspěšné ladění předpokládá přítomnost ladících informací (debug symbols) ve zkoumané aplikaci. Nezapomeneme je proto přidat volbou -g během její kompilace. Zároveň je vhodné vypnout veškeré volby, které se týkají optimalizace kódu kompilátorem (-O_). Při zapnuté optimalizaci se totiž může chování gdb jevit jako poněkud bláznivé. Stává se například, že hodnota je proměnné přiřazena dříve, než je dosaženo místo přiřazení v kódu apod. Zvláště zábavné je toto chování při ladění v nějakém grafickém prostředí, kdy se ukazatel aktuální pozice v kódu chová z pohledu vývojáře naprosto nepředvídatelně. Důvodem je to, že zdrojový kód, který vývojář vidí, se může značně lišit od strojového kódu, který je skutečně vykonáván.
Ukažme si práci s gdb na ukázce ladění aplikace obsah (tzv. debug session), která vypočítá obsah obdelníku na základě délky jeho stran zadaných jako parametry aplikace. Zdrojový kód aplikace je na výpisu 3.
1#include <stdio.h> 2#include <stdlib.h> 3 4int main (int argc, char ** argv) 5{ 6 int a, b, res; 7 8 if ( argc != 3 ) 9 { 10 return EXIT_FAILURE; 11 } 12 13 a = atoi(argv[1]); 14 b = atoi(argv[2]); 15 16 // spocitame si obsah obdelniku a zobrazíme výsledek 17 res = a * b; 18 printf("Obsah obdelniku je %d\n", res); 19 20 return EXIT_SUCCESS; 21}
Ladění aplikace spustíme takto:
$ gdb -q obsah
Poznámka: gdb hledá zadanou aplikaci ve svém aktuálním adresáři, což je adresář, odkud byl spuštěn. Proto se gdb obvykle spouští z adresáře, v kterém je aplikace umístěna.
Stručně si popišme průběh ladění (viz obrázek 1.4):
- Spustili jsme gdb s volbou -q a názvem aplikace, kterou hodláme ladit. Volba -q vypne výpis hlavičky gdb, kde jsou licenční ujednání, konfigurace gdb apod.
- Gdb se spustil a vytvořil podřízený proces, kterým je naše aplikace. Aplikace zatím neběží.
- Příkazem set args 2 3 předáme parametry naší aplikaci.
- Příkazem break main nastavíme breakpoint na začátek funkce main().
- Spustíme aplikaci - příkazem run.
- Gdb spustí aplikaci a pak zastaví její běh na řádku 8 (viz výpis 3), kde je umístěn breakpoint. Nenechme se zmýlit tím, že gdb občas ukazuje číslo řádku o jedničku vyšší.
- Příkazem list si necháme vypsat zdrojový kód okolo aktuální pozice.
- Nastavíme další breakpoint na řádek č. 13 ve zdrojovém souboru obsah.c.
- Dovolíme aplikaci pokračovat dál až do dalšího breakpointu (příkaz Continue).
- Příkazem next vykonáme další krok, tj. přiřadíme proměnné a hodnotu.
- Příkazem print a si zobrazíme aktuální hodnotu proměnné a.
- Necháme pokračovat aplikaci v běhu. Protože není nastaven žádný další breakpoint, tak aplikace doběhne do konce, o čemž jsme informováni zprávou ”Program exited normally.”.
- Práci s gdb ukončíme příkazem quit.
Samozřejmě, že gdb, jako mocný a rozsáhlý, nástroj toho umí mnohem více, ale detailnější popishttp://sourceware.org/gdb/current/onlinedocs/gdb/ jeho vlastností je nad rámec tohoto textu.
Ladění spuštěného programu
Zajímavou vlastností gdb je možnost připojit se k programu, který už běží, a začít jej ladit. To se hodí v situacích, kdy se naše aplikace začne chovat podivně a my chceme zjistit proč. Než aplikaci zastavit a znova ji pustit pod dohledem gdb, tak je lepší nechat ji běžet a připojit se k ní debuggerem gdb. Je tak větší šance, že odhalíme příčinu špatného chování.
Postup je patrný z obrázku 1.5, kde zkoumaným programem je příkazový intepreter bash:
- Nejprve si zjistíme identifikační číslo (PID) zkoumaného procesu (tady pomůže utilita ps).
- Pak spustíme debugger gdb a příkazem attach PID se připojíme ke zkoumanému programu. Od tohoto okamžiku je program pozastaven a jeho běh je nadále kontrolován gdb.
- Od zkoumaného programu se odpojíme příkazem detach. Tím se gdb vzdá kontroly nad programem, který tak volně pokračuje dál.
Seznam základních příkazů gdb
Níže je uveden seznam základních příkazů. V závorce je vždy uvedena zkratka, pokud mi byla známa.
- break funkce (b) - vlož breakpoint na začátek funkce.
- break soubor:řádek (b) - vlož breakpoint na příslušné místo.
- clear číslo řádku - smaž breakpoint na daném řádku.
- continue (c) - pokračuj k dalšímu breakpointu nebo do konce programu.
- next (n) - krok bez zanoření do volané funkce.
- step - krok se zanořením do volané funkce.
- list (l) - výpis kódu programu v okolí aktuální pozice.
- print výraz (p) - výpis hodnoty výrazu.
- detach - odpojí gdb od laděné aplikace. Aplikace běží dál.
- disconnect - odpojí gdb od laděné aplikace. Aplikace zůstává dál ve stále stejném stavu. Je možné se k ní připojit znovu a pokračovat dál v ladění.
- quit (q) - ukonči gdb.
- help (h) - nápověda, může mít jako parametr příkaz nebo skupinu příkazů.
Poznámka: Příkazový interpretr gdb má historii příkazů, takže není nutné ty stejné příkazy vypisovat stále dokola.