Ladění programu pomocí gdb

Z Milan Kerslager
Přejít na: navigace, hledání

Ladění programu pomocí gdb je vhodné pro práci v příkazovém řádku. Debugger gdb slouží pro krokování programu, inspekci proměnných, ale i analýzu core (soubory s obrazem paměti po pádu programu). Existují nadstavby jak pro textové rozhraní (např. cgdb), tak pro grafické rozhraní (např. ddd) nebo je možné ladit program přímo v integrovaném vývojovém prostředí (Eclipse). Text se bude dále zabývat použitím debuggeru přímo na příkazovém řádku.

Ladění programu

Program, který chceme ladit pomocí gdb, je nutné přeložit s přepínačem -g. Program natáhneme do debuggeru příkazem gdb vypocet, načež je zobrazena licence a aktivuje se interní řádkové rozhraní debuggeru (v následujícím příkladu je vstup uživatel vyznačen tučně):

$ gdb vypocet
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-23.el5_5.2)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/kerslage/vyuka/pre/vypocet...done.
(gdb) 

Pokud by program gdb vypíše hlášení no debugging symbols found, je potřeba zkontrolovat zadání parametru -g při překladu jako parametr (pro překladač gcc). V interním řádkovém rozhraní je možné zadávat příkazy, kterými je gdb řízen (viz dále).

Krokování programu

list
Příkaz list vypíše zdrojový kód programu včetně čísel řádků. Parametrem může být číslo řádku, jméno funkce, dvojice soubor:jménofunkce nebo rozsah řádků oddělený čárkou.
break
Příkaz break slouží k vytvoření bodu přerušení v programu (též zarážka). Parametrem může být číslo řádku nebo název funkce a oboje může být podobně jako u příkazu list upřesněno názvem souboru s dvojtečkou. Za parametr může být doplněno klíčové slovo if, za kterým je možné předepsat podmínku pro přerušení (např. x>5).
watch
Příkaz watch definuje výraz, při jehož splnění je program pozastaven (např. x>5).
clear
Příkaz clear slouží ke zrušení bodu přerušení definovaného příkazem break.
next
Příkaz next spustí program až do začátku následujícího příkazu ve zdrojovém kódu. Pokud je na aktuálním řádku volání funkce, je tato provedena celá.
step
Příkaz step udělá v programu jeden krok (tj. jeden příkaz). Pokud je na aktuálním řádku volání funkce, vstoupí se do ní a provádění se zastaví (bude možné funkci krokovat).
until
Příkaz unitl spustí program až do specifikovaného místa (číslo řádku, název funkce).
finish
Příkaz finish způsobí, že aktuální funkce je dokončena.
continue
Pokračování běhu programu (až do dalšího bodu přerušení).
run
Příkaz run slouží ke spuštění programu, který je do gdb natažen. Případné parametry uvedené za příkazem run jsou programu předány, jako by byl s nimi spuštěn z příkazového řádku.
record
Příkaz record umožňuje registrovat změny, které umožní vracet se v programu zpět. Příkaz zadejte ihned po příkazu run. Pak je možné použít příkazy reverse-next, reverse-step a další.

Inspekce proměnných a paměti

print
Příkaz print slouží k vypsání obsahu proměnné, která je zadána jako parametr. Předřazením znaku & je vypsána adresa, na které je proměnná umístěna (spolu s datovým typem).
info locals
Příkaz info locals vypíše všechny lokální proměnné v aktuálním kontextu programu.
info proc mappings
Příkaz info proc mappings vypíše mapu paměti procesu.
info files
Příkaz info files vypíše informace o souborech namapovaných do paměti (včetně knihoven).
maintenance info section
Příkaz maintenance info section vypíše informace o regionech paměti (pro čtení, pro zápis, …).
x
Příkaz x slouží k inspekci obsahu paměti. Parametrem může být adresa v paměti, název funkce nebo znak & následovaný jménem proměnné (vypíše adresu paměti a její obsah). Modifikátory umožňují různý výpis obsahu paměti (čísla, strojové instrukce, znaky) – např. x/5c 0x400500 vypíše 5 znaků z udané hexadecimální adresy.
backtrace
Příkaz backtrace vypíše obsah zásobníku ve smyslu seznamu aktuálního stavu volání funkcí (tj. všechna volání funkcí, resp. návratů z funkcí). Při zpracování core zobrazí číslo řádku v kódu, na kterém došlo k chybě.

Ostatní

quit
Příkaz quit slouží k ukončení činnosti gdb.
kill
Příkaz kill ukončí běh právě zpracovávaného programu.
help
Příkaz help vypíše nápovědu. Parametrem může být libovolný příkaz.
info
Příkaz info vypisuje různé informace o stavu debuggeru. Parametrem může být například args (argumenty programu z příkazového řádku), breakpoints (výpis bodů přerušení), watchpoints (dtto), registers (obsah registrů procesoru), threads (spuštěné thready), set (interní nstavení gdb).

Příklady ladění programu

Pro ladění je možné použít pohodlnější grafickou nadstavbu debuggeru, například ddd (data display debugger) nebo nějaké vývojové prostředí (např. Eclipse).

Krokování a inspekce proměnných

Mějme zdrojový kód programu v souboru vypocet.c:

#include <stdio.h>

int main()
{
  int a, x = 3;
  a = x + 5;
  printf("Vysledek: %d\n", a);
  return 1;
}

Pak ho přeložíme spolu s ladícími informacemi (pomocí parametru -g):

gcc -g -o vypocet vypocet.c

Dále je záznam práce s gdb, přičemž tučně jsou zvýrazněny vstupy uživatele. Komentář je pod příkladem (čísla řádků zcela vlevo jsou přidána dodatečně, aby bylo možné výpis komentovat):

 1  $ gdb vypocet
 2  Reading symbols from /home/ke/pokus/vypocet...done.
 3  (gdb) break main
 4  Breakpoint 1 at 0x4004cc: file vypocet.c, line 5.
 5  (gdb) run
 6  Starting program: /home/ke/pokus/vypocet  
 7  
 8  Breakpoint 1, main () at vypocet.c:5
 9  5         int a, x = 3;
10  Missing separate debuginfos, use: debuginfo-install glibc-2.13-1.x86_64
11  (gdb) print a
12  $1 = 0
13  (gdb) print x
14  $2 = 0
15  (gdb) next
16  6         a = x + 5;
17  (gdb) print a
18  $3 = 0
19  (gdb) print x
20  $4 = 3
21  (gdb) next
22  7         printf("Vysledek: %d\n", a);
23  (gdb) print a
24  $5 = 8
25  (gdb) print x
26  $6 = 3
27  (gdb) continue
28  Continuing.
29  Vysledek: 8
30  
31  Program exited with code 01.
32  (gdb) quit
33  $

TODO: kometář příkladu

Práce s core

Mějme chybný zdrojový kód programu v souboru sigsegv.c, který způsobí pád programu na porušení ochrany paměti (SEGV, tj. segmentation fault):

#include <stdio.h>

int main()
{
  char *adresa = "Ahoj";
  // následuje zápis do text segmentu (paměť jen pro čtení) -> SEGV
  strcpy(adresa, "BAF");
}

Pak ho přeložíme spolu s ladícími informacemi (pomocí parametru -g):

gcc -g -o sigsegv sigsegv.c

Dále je záznam práce na příkazovém řádku, přičemž tučně jsou zvýrazněny vstupy uživatele. Komentář je pod příkladem (čísla řádků zcela vlevo jsou přidána dodatečně, aby bylo možné výpis komentovat):

 1  $ ulimit -c unlimited
 2  $ ./sigsegv
 3  Neoprávněný přístup do paměti (SIGSEGV) (core dumped [obraz paměti uložen])
 4  $ gdb -c core.8847 sigsegv
 5  Reading symbols from /home/ke/Dropbox/Dokumenty/Výuka/TUL/c/sigsegv...done.
 6  [New LWP 8847]
 7  Core was generated by `./sigsegv'.
 8  Program terminated with signal SIGSEGV, Segmentation fault.
 9  #0  0x0000000000400500 in main () at sigsegv.c:8
 9  8		strcpy(adresa, "BAF");
10  (gdb) backtrace
11  #0  0x0000000000400500 in main () at sigsegv.c:8
12  (gdb) quit

Na řádku 1 je povolen zápis neomezeně velkého souboru core (obraz paměti v okamžiku pádu programu) do aktuálního adresáře. Na řádku 4 je spuštěn debugger s parametrem pro načtení core, po kterém debugger informuje o zjištěné příčině pádu programu a řádku, na kterém k tomu došlo (starší verze debuggeru vyžadují použití příkazu backtrace). Soubor core má jako příponu číslo havarovaného procesu, takže nový výpis nepřepíše ten starší.