Syntax highlighting of Archiv/Ladění pod UNIXEM - gdb, mtrace

==Proč tohle==

Toto je referát na seminář linux. Měl by pomoct lidem, kteří se chtějí naučit ladit a nechce se jim (světe div se) číst originální manuál.
http://www.gnu.org/software/gdb/documentation/

==Programy a chyby==

Téměř každý program má svoje chyby. Chyby mohou být jak v návrhu (program tak jak je navržen nefunguje správně),
tak v realizaci (chyby v kódu). Praktičtější je ovšem chyby rozdělit na chyby "aktivní" a pasivní. Do první 
skupiny patří chyby patrné na první pohled, tj. program nedělá to co má, nebo padá. Do druhé skupiny patří typicky
chyby v alokaci a uvolňování paměti. Druhou skupinu je výrazně obtížnější jak detekovat, tak odhalit.
===Fáze ladění kódu===
Vytváření a ladění nového software v podstatě probíhá podle následujícího schématu:
 DOPSAL JSEM SOFTWARE                                                                   
 TESTUJI HO                                                                             
 if (V PROGRAMU JE CHYBA) goto ZACATEK LADENI else goto KONEC                           
  ZACATEK- LADENI                                                                       
  STABILIZUJ CHYBU //zajisteni aby se chyba opakovala                                  
  LOKALIZUJ CHYBU  //nalezeni příslušné knihovny, funkce, řádku, kde k chybám docházi  
  OPRAV CHYBU                                                                          
 goto TESTUJI HO                                                                       
 KONEC //program je odladěný a můžeme začít s distribucí.

===Možnosti ladění kódu===
====Expertní čtení====
Nejjednodušší je tzv "expertní čtení", aneb "Strécu, ono to nefacha, tak se do toho hleď ještě mrknout.". Tohle je jistě
možnost, ale má nevýhodu zejména psychologickou, pokud jsme udělali logickou chybu v návrhu, je poměrně těžké ji 
odstranit jen čtením kódu, protože se nám kód zdá v pořádku. Je to (prý) poměrně funkční v případě, že kód po nás čte 
někdo jiný.
====Ladící výpisy====
Asi nejobecnější možnost ladění, funguje skoro vždy a skoro všude. Je dobré vypisovat na stderr, pač jinak pokud 
něco vypíšete na stdin a program pak "spadne", hláška se může v principu ztratit (neměla by, ale experimentálně je 
tohle tvrzení ověřené).
Do programu je možné zařadit výpisy na místa, kde čekáme, že by mohlo dojít k chybě.
Docela rozumné může být využít makroprocesoru překladače, napřiklad konstrukcí 

 #ifdef DEBUG
   printf ("hodnota x je : %d\n", x);
 #endif

a makro DEBUG si definovat třeba při překladu

 $ cc -o sorter -DDEBUG main.c
A jinak v jazyce c/c++  jsou k dispozici makra
{| border=1 cellspacing=0 cellpadding=5
| __LINE__
| Číslo aktuální řádky
|-
| __FILE__
| Jméno souboru
|- 
| __DATE__
| datum, Řetězec formátu "Mmm dd yyyy"
|-
| __TIME__
| čas, Řetězec formátu "h h : mm : s s"
|-  
|}

====Ladící nástroje====
Jsou asi nejúčinnější formou ladění, (pokud jsou dostupné), pod unixovými systémy je obvykle k dispozici pro ladění 
chyb, které se projevují db, který má pod Linuxem obdobu gdb. Na skryté chyby, např v alokaci paměti jsou k dispozici
nástroje typu mtrace, nebo knihovna electric fence (možná pár slov na konci).
Program gdb nám umožňuje pozorovat co se děje v "programu vevnitř", popřípadě toto dokonce měnit.

==Ukázkový příklad 1.==
main.c

 #include <stdio.h>
 #include <stdlib.h>
 #include "qsort.h"
 int main(int argc, char ** argv){  
  int i;
  int cisla[] = {10, 12, 2, 4, 8,7, 1, 20, 3, 25};
  int *  cilsa;
  int arrsize = sizeof(cisla) / sizeof(int); 
  printf("\n\nhello, Im Program for sorting.. ");
  
  printf("\nthis is my input  :");
  for (i=0; i < arrsize; i++){
    printf("%d ",cisla[i] );
  }
  //tady se to celý děje.
  xquickSort(cilsa,  arrsize);
  
  printf("\nthis is my output :");
  
  for (i=0; i < arrsize; i++){
    printf("%d ",cisla[i] );
  }
  printf("\nDo you think it's OK?\n");
  return 0;
 }

qsort.c
 void xq_sort(int numbers[], int left, int right){
  int pivot, l_hold, r_hold;
  l_hold = left;
  r_hold = right;
  pivot = numbers[left];
  while (left < right)
  {
    while ((numbers[right] >= pivot) && (left < right))
      right--;
    if (left != right)
    {
      numbers[left] = numbers[right];
      left++;
    }
    while ((numbers[left] <= pivot) && (left < right))
      left++;
    if (left != right)
    {
      numbers[right] = numbers[left];
      right--;
    }
  }
  numbers[left] = pivot;
  pivot = left;
  left = l_hold;
  right = r_hold;
  if (left < pivot)
    xq_sort(numbers, left, pivot-1);
  if (right > pivot)
    xq_sort(numbers, pivot+1, right);
 
 }

qsort.h

 extern void xquickSort(int numbers[], int array_size);
Makefile

 EXECUTABLE	        = sorter 
 SOURCES		= main.c qsort.c
 HEADERS		= qsort.h
 OBJECTS		= $(SOURCES:.c=.o)
 CC		        = gcc
 CFLAGS		        = -ggdb -Wall
 LDFLAGS		= -o 
 all: $(EXECUTABLE) 
 clean:
       rm *.o $(EXECUTABLE) 
 $(EXECUTABLE): $(OBJECTS)
       $(CC) $(LDFLAGS) $@ $^
 $(OBJECTS): $(SOURCES) $(HEADERS)


==Překlad programu==
Program se nám přeloží bez chyb nebo varování. Je dobré poznamenat,že program je dobé přeložit s parametrem 
 -g, 
nebo pro gdb ještě lépe 
 -ggdb nebo -ggdb3
. Ty volby se liší ve formátu ladících informací vkládaných do souboru.  Do programu takto budou vkládat i některé informace "navíc",které ladícímu prostředku umožní sledovat hromadu věcí, jako jsou třeba jména proměnných a podobně
Mimo jiné by šlo ještě poznamenat, že pokud program nechodí tak jak má je dobré ho přeložit s volbou 
 -Wall -pedantic -ansi
, které nám mohou pomoci odhalit i některé neobvyklé konstrukce, často chyby.

== Nástroj gdb ==
gdb je program pro ladění jiných programů. Programy jdou spustit jakoby "v něm". Někdo tvrdí, že je gdb 
nepřekonatelné... Já bych byl střízlivější, pro člověka, který není líný se něco naučit, a je schopen
překonat deprese z příkazové řádky je to mocný nástroj.
=== Co umožňuje gdb ===
Ladit přeložené programy, zkoumat core-dumpy a připojovat se k běžícím programům. Poslední možnost je docela praktická zvláště pokud chceme ladit třeba serverové aplikace.

=== Co se děje při startu gdb ===
Než začnu rozebírat přikazy pro gdb, pár slov k tomu, co se děje při startu gdb.
#gdb se rozběhne v příslušném módu (třeba s GUI, nebo v módu silent).. (o GUI nic netuším ,ale je to v manuálu)přečte si konfigurák v $HOME
#zpracuje argumenty příkazové řádky
#přečte si konfigurák v aktuálním adresáři
#načte historii příkazů pokud ji máme zapnutou

=== Konfigurační soubor ===
A teď k tomu konfiguráku: na Linuxu se jmenuje vždycky stejně, .gdbinit a dá se do něj nacpat spoooousta zajímavejch věcí.
No třeba ta historie :

 set history filename ./.gdb_history
 set history save on 

Docela dobry je si do konfiguráku v daném adresáři dát který soubor bude laděn
 file ./sorter

Nebo preloadované knihovny
 set env LD_PRELOAD=/usr/lib/libefence.so

Nebo breakpointy
 b main

Dají se tem taky definovat makra, ale to je jiná kapitola.
Komentáře jsou jako ve skriptech - uvozené #

=== Spuštění gdb ===
celá ta nádhera se spustí pomocí příkazu gdb. Volby jsou následující :
{| border=1 cellspacing=0 cellpadding=5
| -s file
|Ze souboru file se načte tabulka symbolů 
|-
| -e file
|Soubor file bude laděn , možno kombinovat se s.
|-
| -c N
|Gdb se připojí k již běžícímu procesu s PID N 
|-
| -c file
|Gdb načte core soubor file 
|-
| -x file
|Gdb vykoná gdb příkazy v souboru file, něco jako možnost skriptování 
|-
| -d dir
|K adresářům, v nichž gdb hledá zdrojové kódy přidá dir 
|-
| -m
|Načte symboly do souboru, který pak používá jako tabulku symbolů 
|-
| -r
|Načte všechny symboly okamžitě, místo čtení až v případě potřeby 
|-
| -n,-nx
|GDB nevykoná žádné příkazy z inicializačních souborů
|-
| -q
|Nevytiskne po spuštění úvodní text a informace o copyrightu 
|-
| -batch
|Po vykonání příkazů v souboru -x file vrátí 0, při chybě vrátí nenulové číslo 
|-
| -cd dir
|Změní aktuální adresář na dir 
|-
| -f
|Vypisuje plné jméno souboru a číslo řádky vždy při zastavení programu 
|-
| -b bps
|Nastaví rychlost seriového portu na bps pro vzdálené ladění 
|-
| -tty dev
|Přesměruje stdout a stdin laděného programu na dev 
|}
Kdo jste chodila na základy překladačů tak určitě víte co jsou to tabulky symbolů.
Je to taková ta chytrá věc, která pojí to jak se proměnná jmenuje, a to jakou má hodnotu.
Docela fajn věc pro takové ladění. Víc to tu nehodlam rozebírat pro zájemce je pěkný článek o tom, jak to funguje ve binárkách v Linuxu na téhle adrese: http://www.manualy.sk/seminar/Papers95/elf/

Jinak většinou budete startovat gdb příkazem
 gdb file
, kde file je jméno laděného programu.

=== Příkazy v gdb ===
No a teď zvesela na všechny možný příkazy a fíčury gdb.
příkazy se zadávají ručně a gdb si je kompletuje, tj, pokud stisknete 
 h
a 
 help 
je jediný příkaz začínající na h
tak to gdb pochopí. Pokud mu dáváte ještě vybrat,tj. je víc příkazu začínající danou sadou znaků, tak  gdb vypíše možnosti.

=== Příkaz help ===
První příkaz, který je dobrý znát je vyvolání helpu. 
help se vyvolá třeba 
 h
Tenhle příkaz vypíše seznam všech skupin do kterých jsou příkazy dělené. 
 h <skupina> 
zobrazí příkazy ve skupině.
 h <příkaz>  
zobrazí krátký popis příkazu.
Ukázka:
Chceme se dozvědět jak se nastavuje breakpoint na nějakou řádku.
 (gdb) h
 aliases -- Aliases of other commands
 breakpoints --
 ....
 (gdb) h breakpoints
 awatch -- Set a breakpoint for an expression
 break -- Set a breakpoint at specified line or function
 ....
 (gdb) h break
nám vrátí podrobný popis jak dát breakpoint na řádku

=== Příkazy k použití pro započetí ladění ===
Pokud už nám gdb běží (a nespustili jsme ho gdb file) tak musíme specifikovat co se vlastně bude ladit.

{| border=1 cellspacing=0 cellpadding=5
|exec-file file
|otevře file pro ladìní 
|-    
|core-file file
|otevře core file
|- 
|attach pid
|připojí se k procesu pid 
|}

Programu je možné nastavit argumenty a pracovní adresář.

{| border=1 cellspacing=0 cellpadding=5
|set arg
|nastaví argumenty programu
|- 
|show arg
|zobrazí argumenty programu
|- 
|set environment
|nastaví proměnné prostředí
|- 
|show environment
|zobrazí proměnné prostředí 
|-
|cd dir
|nastaví adresář dir jako aktuální 
|-
|pwd
|zobrazí aktuální adresář
|}

A potom už můžeme program vesele spustit, a ladit daný program.

{| border=0 cellspacing=0 cellpadding=5
|run
|spustí program 
|}


Ukázka:

 (gdb) exec-file sorter
 (gdb) set arg Evzen
 (gdb) cd ../
 (gdb) run   
Poznámka: když jsem tohle zkoušel tak se mi to jevilo, jako když gdb nenašel v souboru  ladící informace,pokud byl spuštěn bez argumentù a pak mu byl dán příkaz exec-file.Že by bug v debuggeru :-) ???

=== Breakpointy, watchpointy a jiné zastavování běhu programu ===
Breakpointy a watchpointy jsou místa, kde si přejeme program zastavit. 
Breakpointy jsou nastavení na nějakou funkci nebo řádek, zatímco watchpointy se vztahují k nějakým proměnným.
Co je zajímavý rozdíl, je jak je debugování pomocí watchpoinů a breakpointů  realizované zevnitř. 
Obě skupiny mohou být realizovány hardwarově a softwarově. Hardwarové řešení vyžaduje podporu hardware, a proto není možné ho zapnout vždy.(softwarově znamená, že do programu je vložena nějaká instrukce, což samo o sobì mùže být značně nežádoucí )Pro hardwarové wp jsou
poměrně značné omezení, protože využívali registry, mohou být jen dva a to ještěì stejného typu. 

==== Breakpointy ====
{| border=1 cellspacing=0 cellpadding=5
|break, b
|    nastaví bp na další řádku od aktuální v daném podprogramu (funkci) 
|-
|b funct
|    nastaví bp na funkci "funct" 
|-
|b N
|    nastaví bp na řádek N ve zdrojovém kódu 
|-
|b +N, -N
|    nastaví bp o N řádek dopředu / dozadu od aktuální řádky 
|-
|b file:funct
|    nastaví bp na funkci funct definovanou v souboru file 
|-
|b file:N
|    nastaví bp na řádek N v souboru file 
|-
|b *addr
|    nastaví bp na adresu addr (tohle je praktické, zvlášť pokud Vás něco vede k ladění programu,přeloženého bez -g)
|-
|b if cond
|    nastaví podmíněný bp, tzn. bp zastaví běh programu, pouze je-li splněna podmínka cond
|-
|tbreak args
|    nastaví bp se stejnými argumenty jako výše, ale pouze na jedno použití, pak je bp vymazán 
|-
|hbreak args
|    nastaví hardwarový bp, hw bp musí být podporován hardwarem, oproti klasickému bp nemění 
instrukci, na které je nastaven
|- 
|thbreak args
|    hardwarový tbreak
|- 
|rbreak regex
|    nastaví bp na všechny funkce, které vyhovují výrazu regex, dodatečně je možno doplnit podmínky a příkazy
|- 
|info breake [n]
|    zobrazí všechny bp a jejich případné podmínky, je-li specifikováno číslo bp, zobrazí informace pouze o něm
|- 
|}

==== Watchpointy ====
K zastavení programu kdykoliv při změně nějaké proměnné je možno použít tzv. watchpointy.
{| border=1 cellspacing=0 cellpadding=5
|watch expr
|    vytvoří wp pro proměnnou expr; gdb zastaví program, kdykoli se obsah proměnné expr změní 
|-
|rwatch expr
|     program je zastaven, kdykoli je proměnná expr čtena 
|-
|awatch expr
|     program je zastaven při jakémkoli přístupu k proměnné (r/w) 
|-
|info watchpoints
|    zobrazí iformace o všech wp, je-li specifikováno číslo wp, vypíše informace pouze o něm 
|}
==== Výjimky ====

Program je možno zastavit i při volání obsluhy výjimky
{| border=1 cellspacing=0 cellpadding=5
|catch exeptions
|    nastaví bp na aktivní obslužné rutiny výjimek, jejichž jména jsou v seznamu exceptions 
|-
|info catch
|    vypíše všechny aktivní obslužné rutiny vyjímek 
|}

Ukázka:

 $ gdb sorter
 ...
 (gdb) watch cisla
  No symbol "cisla" in current context
 (gdb) break main
 (gdb) break xquikSort
 (run)
Nastaví breakpoint na funkce main, a xquicSort, a watchpoint na proměnnou čísla. Ta hláška je logická, proměnná
cisla začne mít smysl až po vstupu do funkce main. Po spuštění se program zastaví na funkci main, poté pči inicializaci
symbolu cisla.

=== Podmínky pro breakpointy, příkazy pro breakpointy ===
Každý brejkpojnt a watchpoint má svoje číslo. Vypsat jakému patří jaké jde třeba příkazem 
 info watch
Víc toho řeknu později
Poměrně praktické je definovat si podmínky pro breakpointy(tj, bp se bude chovat jako bp 
pouze pokud je splněna podmínka).  Syntax podmínek je převzatý z jazyka c.
{| border=1 cellspacing=0 cellpadding=5
|condition bnum expr
|    nastaví podmínku odpovídající výrazu expr pro bp s číslem bnum 
|-
|condition bnum
|    zruší podmínku u bp číslo bnum 
|-
|}

Speciální forma podmínky je zastavení až po určitém počtu průchodů.
{| border=1 cellspacing=0 cellpadding=5
|ignore bnum count
|    bp s číslem bnum bude aktivní až po count průchodech (count je integer)
|}
 
Navíc pokud jde nastavit i co se má stát, pokud program dorazí k breakpointu, popř pokud je daná 
podmínka splněna.
Syntax je následující:

 commands [bnum]
   <seznam příkazů>
 end

Ukázka
  break xquickSort if numbers == 0    #nastav breakpoint, zastav program pokud numbers == 0 
  commands
  silent 	                      #nevypisuj informace o provedených operacích
  set var x = 1 	              #nastav nějakou proměnnou
  echo "je to spatny"                 #něco vypiš
  cont 	                              #pokračuj
  end

=== Správa breakpointů (vymazání, disablování) ===
Příkazy clear logicky nefungují na wp.
{| border=1 cellspacing=0 cellpadding=5
|clear
|    vymaže následující bp v aktuálním podprogramu 
|-
|clear funct
|    vymaže bp na dané funkci 
|-
|clear N
|    vymaže bp na řádce N 
|-
|clear file:funct
|    vymaže bp pro funkci funct v souboru file 
|-
|clear file:N
|    vymaže bp na řádce N v souboru file 
|-
|delete
|    vymaže všechny bp 
|-
|delete bnums
|    vymaže bp s čísly v seznamu bnums 
|-
|disable
|    vypne všechny bp 
|-
|disable bnums
|    vypne bp s čísly v seznamu bnums 
|-
|enable
|    zapne všechny bp 
|-
|enable bnums
|    zapne bp s čísly v seznamu bnums 
|-
|enable once [bnums]
|    zapne bp pouze na jedno použití - buď všechny, nebo bp s čísly bnums 
|-
|enable delete [bnums]
|    zapne bp pouze na jedno použití, pak bp smaže všechny, nebo bp s čísly bnums 
|}
Ukázka

 (gdb) info watch
 Num   Type          Disp  Enb Address     What
 1     watchpoint    keep  y               cisla
 2     hw wachpoint  keep  y               i
 3     breakpoint    keep  y   0x08048394  in main at main.c 11
 (gdb) clear main
 (gdb) delete 1 2
 (gdb) info watch
 No breakpoints or watchpoints

=== Krokování programu ===
Je-li program zastaven, může být krokován nebo pokračovat ve svém běhu. K těmto účelům slouží tyto příkazy:
{| border=1 cellspacing=0 cellpadding=5
|step [N]
|    vykoná jeden nebo N řádek, je-li volána funkce, příkaz pokračuje uvnitř této funkce - zanoří se 
|-
|next [N]
|    vykoná jeden nebo N řádek bez zanoření 
|-
|finish
|    dokončí aktuální stack frame a vytiskne návratovou hodnotu 
|-
|until
|    zastaví vykonávání programu až na další řádce, dobré pro smyčky 
|-
|until location
|    zastaví vykonávání programu až na řádce odpovídající location (číslo řádky, jméno funkce) 
|-
|stepi
|    provede jednu strojovou instrukci s případným zanořením 
|-
|nexti
|    provede jednu strojovou instrukci bez zanoření 
|-
|continue
|    pokračuje ve vykonávání programu až do dalšího zastavení (např bp.) 
|-
|continue N
|    ignoruje N krát daný breakpoint 
|-
|jump cislo
|    skočí na řádek cislo 
|-
|jump *addr
|    skočí na adresu addr 
|}

=== Zobrazení zdrojového kódu přímo v programu gdb ===
Pokud opravdu pracujeme v konzoli, tak je docela praktické si prohlédnout zdrojový kód programu, pokud jsme se 
nejakým způsobem zastavili na proglematickém místě.
K tomuto účelu slouží příkaz list.
{| border=1 cellspacing=0 cellpadding=5
|list
|    vypíše 10 řádek zdrojového kódu, počínaje aktuální řádkou, opakováním tohoto příkazu se vypíše dalších 10 řádek 
|-
|list -
|    vypíše 10 řádek zdrojového kódu před aktuální řádkou včetně 
|-
|list N
|    vypíše 10 řádek zk počínaje řádkou N 
|-
|list funct
|    vypíše 10 řádek zk počínaje první řádkou definice funkce funct 
|-
|list *addr
|    vypíše řádek zk, který obsahuje adresu addr 
|-
|list first, last
|    vypíše zdrojový kód od řádky first do řádky last 
|-
|list ,last
|    vypíše zdrojový kód od aktuální řádky do řádky last 
|-
|list first,
|    vypíše zdrojový kód od řádky first do aktuální řádky 
|-
|set listsize N
|    změní vypisovaný počet řádek na N 
|-
|show listsize
|    zobrazí, kolik řádek se bude vypisovat 
|-
|search regexp
|    vypíše  následující řádek, na kterém najde regexp
|-
|reverse-search regexp
|    vypíše  předchozí řádek,na kterém najde regexp
|-
|}

Ukázka
 (gdb)search printf
 23      printf("\nthis is my input:  ");

Co je docela dobrý, že tímhle se posune kontext gdbcka na příslušný řádek, a tedy pokud máme zapnutou history file, tak se dá ten příkaz zase vyvolat a může se jet k dalšímu takovému symbolu.

=== Zobrazování kódu programu v asembleru ===
Zobrazování kódu programu v asembleru
Pro opravdové labužníky je možnost vypisovat si program přímo v asembleru. Tahle možnost pracuje jen pokud je 
program přeložen s ladícími informacemi(nevíte někdo proč??). Pokud nemáme ladící informace k dispozici, můžeme ještě zkusit vypisovat přímo paměť a nechat jí interpretovat jako instrukce, ale to je opravdu hodně pokročilá a nejistá věc.

=== Zkoumání obsahu paměti programu ===
==== Formát dat ====
gdb musí v některých případech vědět, v jakém formátu má zobrazit data. toto shrnuje následující tabulka:
{| border=1 cellspacing=0 cellpadding=5
|x
|vypíše obsah jako číslo v hexadecimální soustavě
|-
|d
|vypíše obsah jako číslo se znaménkem v decimální soustavě
|-
|u
|vypíše obsah jako číslo bez znaménka v decimální soustavě
|-
|o
|vypíše obsah jako číslo v oktalové soustavě
|-
|t
|vypíše obsah jako číslo v binární soustavě
|-
|a
|vypíše obsah jako adresu
|-
|c
|vypíše obsah jako znak
|-
|f
|vypíše obsah jako číslo s plovoucí čárkou
|-
|i
|vypíše obsah jako instrukci v assembleru
|}

==== Délky datové buňky ====
Pokud vypisujeme data z adresy, tak je potřeba definovat, jak dlouhá data chceme interpretovat.
Délky můžeme nastavit na:
{| border=1 cellspacing=0 cellpadding=5
|b 
|8 bitů
|-
|h
|16 bitů
|-
|w
|32 bitů
|-
|g
|64 bitů
|}

==== Zobrazování hodnot proměnných (print) ====
Nejčastěji se budeme chtít zobrazit proměnnou a její hodnotu.
K tomu nám poslouží příkazy:
{| border=1 cellspacing=0 cellpadding=5
|print exp
|    vypíše hodnotu proměnné nebo výrazu exp
|- 
|print/F exp
|     vypíše hodnotu proměnné nebo výrazu exp ve formátu F 
|}

==== Automatické zobrazování hodnot proměnných (display) ====

Pokud tuto akci chceme automatizovat, tj pokud chceme nějaká data zobrazit po každém zastavení programu na bp nebo wp je pro nás připraven příkaz display

{| border=1 cellspacing=0 cellpadding=5
|display/F addr 
|     nastaví zobrazování obsahu adresy addr ve formátu F při každém zastavení programu, parametr s, který zobrazí obsah adresy jako řetězec 
|-     
|display     
|     vypíše všechny hodnoty existujících "zobrazovačů"
|- 
|undisplay dnums, delete display dnums     
|     smaže "zobrazovače" s čísly dnums       
|-
| disable display dnums     info line N
|     vypne "zobrazovače" s čísly dnums        
|-
|enable display dnums     info line *addr
|     zapne "zobrazovače" s čísly dnums         
|-
| info display
|     vypíše informace o existujících "zobrazovačích"
|}

==== Přímý přístup k datům (x, eXamine) ====
Nejsurovější možností přístupu k datům programu je příkaz x, který sahá doslova a do písmene přímo na data.

{| border=1 cellspacing=0 cellpadding=5
| x/NFU addr
|vypíše obsah adresy addr ve formátu NFU, kde N určuje, kolikrát se má příkaz opakovat, F je formátovací znak, U je velikost datového typu pro danou adresu 
|}
Ukázka
v programu máme pole integerů. pokud chceme vypsat celé pole použijeme příkaz print

 (gdb) print cisla
 $2 = {10, 12, 2, 4, 8, 7, 1, 20, 3, 25}
a teď k nim přistoupíme pomocí příkazu x
 (gdb) x/10uw $2

=== Práce s daty na zásobníku, trasování ===
Pro většinu z Vás asi nebude překvapením, že realizace programu (většinou) probíhá pomocí zásobníku.
Při volání funkce se na zásobník uloží její parametry, návratovou hodnotu, lokální proměnné a adresu, 
na které je funkce právě vykonávána. Čím později je funkce volána, tím blíže k vrcholu zásobníku jsou tyto 
údaje uloženy. 
'''Stack frame''' je název pro tato data vážící se k právě jedné volané funkci. Při spuštění 
programu existuje pouze jeden stack frame pro funkci main. Při každém volání další funkce vzniká nový 
stack frame. Stack frame je jednoznačně určen svojí adresou. V době vykonávání stack framu je jeho adresa 
obvykle uchovávána ve frame pointer registru.
Tolik teorie,a teď jak se to dá použít
Gdb přiřazuje každému stack framu unikátní číslo, nulu pro nejvnitřnější sf, tj. ten, který byl volán naposledy. 
Jednička náleží sf, ze kterého byl poslední volán, atd.
 U některých kompilátorů je možno vypnout používání sf. Gdb má omezené možnosti pro ladění takovýchto programů.
 Následuje seznam příkazů pro manipulaci se stack framy:
{| border=1 cellspacing=0 cellpadding=5
|frame args
|    přesun z jednoho sf do jiného a jeho tisk, args může být adresa sf nebo číslo, není-li 
argument uveden, vytiskne se aktuální sf
|- 
|select-frame
|    přesun z jednoho sf do jiného bez tisku 
|-
|bt, bt N
|    back trace - vytiskne N nejvnitřnějších sf 
|-
|bt -N
|    vytiskne N nejvnějšnějších sf 
|-
|up
|    přesun o jeden sf výše (do volajícího sf) 
|-
|down
|    přesun o jeden sf níže 
|-
|info frame
|    vytiskne podrobné informace o sf 
|-
|info args
|    zobrazí argumenty aktuálního sf 
|-
|info locals
|    zobrazí lokální proměnné aktuálního sf 
|-
|info catch
|    vytiskne aktivní obsluhy výjimek v aktuálním sf 
|}
K nucenému návratu z funkce lze použít příkaz return expr. Příkaz danou funkci nedokončí, ale zruší daný stack frame 
a všechny, které z něj byly volány. Je-li uveden výraz expr, vyhodnotí se a výsledek je použit jako návratová hodnota. 
Následuje návrat za místo volání dané funkce.

Ukázka
 (gdb) run
 Starting program: /afs/ms.mff.cuni.cz/u/p/popxt3am/prg_unix/ladeni/nullptr-lad

 Program received signal SIGSEGV, Segmentation fault.
 0x4655d981 in strtof_l () from /lib/libc.so.6
 (gdb) where
 #0  0x4655d981 in strtof_l () from /lib/libc.so.6
 #1  0x4655af43 in __strtod_internal () from /lib/libc.so.6
 #2  0x4655712d in atof () from /lib/libc.so.6
 #3  0x080483d4 in main (argc=1, argv=0x9fce6044) at nullptr.c:4
 (gdb) frame 3
 #3  0x080483d4 in main (argc=1, argv=0x9fab9cb4) at nullptr.c:4
 4        printf("1/%f is %f\n", 1/atof(argv[1]));
 (gdb) print argv[1]
 $1 = 0x0
 (gdb)

=== Ladění programu v asembleru -pro labužníky ===
Tahle pasáž se týká toho, jak ladit program, ve kterém nejsou ladící informace. To je hodně pokročilá věc, která skýtá mnohá úskalí. Popravdě, tohle jsem já nikdy nepoužil a bohapustě jsem to opsal.
Svým způsobem nevím, proč jsou potřeba ladící informace na krokování programu v asembleru.Ale poradit si jde.

Nejdříve je potřeba zjistit, od které adresy se program spouští. V Gdb lze vstupní adresu zjistit příkazem info target, který kromě jiných údajů vypíše i vstupní adresu - entry point.

Je-li známa adresa, od které je program vykonáván, nastaví se na ni breakpoint a program se spustí. Nyní je možno program krokovat, nastavovat breakpointy, ale nefunguje příkaz 
disassemble. Ten vyžaduje ladící informace ( je-li možno v tomto případě příkaz disassemble zprovoznit, nevím o tom a uvítám případné připomínky).

Krokuje se příkazy si nebo ni. Málokdo však zná binární kód krokovaného programu, aniž by jej předtím viděl. Jak tedy zobrazit výpis v assembleru?

K tomuto účelu dobře poslouží příkaz x (eXamine).

 x/3i $pc	

pc je název registru čítače adresy (program counter) a příkaz x bude zkoumat obsah adresy v něm obsažené parametr i říká, že obsah zkoumané adresy bude zobrazen jako instrukce assembleru
číslo 3 určuje, že budou zobrazeny 3 instrukce od adresy v pc

Příkaz x funguje jednorázově, pro zobrazování instrukce po každém kroku lze použít příkaz display.
 disp /3i $pc	
po každém zastavení programu jsou vypsány tři instrukce od adresy v pc
 disp /5i $pc-2	
bude vypisovat 2 instrukce před aktuální adresou, instrukci na aktuální adrese a 2 instrukce za ní.
!! toto funguje trochu jinak. výraz $pc-2 sahá do paměti dva bajty před aktuální hodnotu čítače instrukcí. Protože instrukce mají různou délku, téměř nikdy se netrefí korektně na začátek instrukce. 
 /3i $pc funguje správně, protože vypisuje 3 instrukce od aktuální hodnoty čítače instrukcí a jejich délku si zjistí sám korektně.

Obsahy základních registrů vypíše příkaz info registers a obsahy všech registrů pak příkaz info all-registers. Jména registrů jsou závislá na platformě, Gdb však zavádí 4 jména, která by měla fungovat na všech platformách. Jsou to
{| border=1 cellspacing=0 cellpadding=5
|pc	
|program counter - v registru je uložena adresa právě vykonávané instrukce
|-
|sp	
|stack pointer - adresa v sp ukazuje na vrchol zásobníku
|-
|fp	
|frame pointer - obsahuje adresu aktuálního stack framu
|-
|ps	
|processor status
|}
Na registry se lze odkazovat jejich jménem jako na běžnou proměnnou. Před názvem však musí být uveden znak "$". Měnit obsahy registrů je možno příkazem 
 set $registr = hodnota 
nebo příkazem print 
 $registr = hodnota
,který zároveň vytiskne novou hodnotu. Např. změnou hodnoty registru pc se skočí na jinou adresu, ale program je stále zastaven.

=== Proměnné ===
K proměnným v programu lze přistupovat přímo jejich jménem. Dereference se zapisuje jako v C pomocí znaku "*". Změnit jejich hodnotu lze příkazy print prom=hodnota a set var prom=hodnota.
Pro různé účely je možno definovat nové proměnné v prostředí Gdb. To se provede příkazem set $prom=hodnota. Vlastní proměnné vypíše příkaz show convenience. Příklad použití vlastních proměnných:

Ukázka
 (gdb) set $i = 0		
 (gdb) print pole[$i++]->prvni

Ukázka
V jednom z předchozích odstavců jsem cosi povídal o výpisu paměti.
použil jsem tam to, že jsem znal velikost pole intů.
pokud bych jí chtěl zjistit, můžu požít příkaz

 (gdb) set $velikost =sizeof(cisla)/4
 (gdb) print $velikost
 $4 = 10		

Zápis hodnoty na konkrétní adresu lze provést příkazem set {type}addr. Ve složených závorkách je uveden typ hodnoty (a tím i velikost). Typy jsou stejné jako v C.

=== Definování maker ===
V Gdb je možno definovat uživatelské příkazy včetně příkazů s argumenty. Slouží k tomu následující příkaz:

 define prikaz
Počátek definice příkazu prikaz. Mohou následovat příkazy gdb, vždy jeden na řádek. Definici ukončuje řádka s příkazem end. 

K argumentům příkazu lze přistupovat pomocí proměnných $arg0, $arg1, atd., kde $arg0 obsahuje hodnotu prvního 
argumentu. Je-li příkazu předáno méně argumentů, než je v definici použito, gdb vypíše chybu "Missing argument x in user function". V příkazu mohou být i podmínky:

 if expr
Je-li splněna podmínka expr, vykonají se příkazy na následujících řádkách. Příkaz if může být následován řádkou s příkazem else. Seznam příkazů pro blok if [else] se ukončí řádkou s příkazem end. 
 until expr
Seznam příkazů za příkazem while, ukončený řádkou s příkazem end, se bude vykonávat, dokud výraz expr bude vyhodnocován jako pravdivý. 

Jsou k dispozici i některé další kostrukce, jako printf, a echo.

Uživatelský příkaz může být dokumentován příkazem 
 document prikaz. 
Příkaz už musí být definován. Text přiřazený  příkazu je pak dostupný v helpu.
Celé sady příkazů jdou načíst pomocí příkazu 
 source.

Prohlédnout příkazy, kterými je definován nějaký uživatelský příkaz, lze příkazem show user prikaz. 
Mimochodem tohle je jedna z věcí, ve které si myslím, že je GDB silnější než grafické ladící nástroje.

Ukázka 
 define vypis_cisla
  if ($arg0 == 1)
   print cisla 
  else
   print cilsa
  end 
 end

 (gdb) vypis_cisla 1
 $2 = {10, 12, 2, 4, 8, 7, 1, 20, 3, 25}

=== Ukázka session -odladění vzorového příkladu ===
To máte za domácí úkol, stejně jsem to už natáhl víc, než jsem chtěl :-)
== Nástroje pro kontrolu dynamicky alokované paměti ==
Protože ne vždy víme, kolik místa budeme v programu potřebovat, je jazyk C obohacen o funkce malloc()
a free() pro alokaci a dealokaci paměti za běhu programu. Bohužel, s dynamickou pamětí je problém, konkrétně jde o to, že
* program sáhne na paměť, kterou nemá alokovanou, tj. způsobí chybu segmentace
* program neuvolňuje paměť a sám se utopí ve množstí alokované paměti, na kterou nevede žádný pointer (popř. způsobí problém systému)

Obecně by mělo platit následující
* program nikdy nepřistoupí k paměti, kterou nemá alokovanou
* program nikdy nepřesáhne meze paměti, kterou má alokovanou
* počet volání malloc a free je vždy stejný

Co je smutné je fakt, že se na chyby v dynamicky alokované paměti špatně přichází
Samozřejmě, že jsou postupy a knihovny, které se tento problém snaží řešit. Já tady popíšu následující tři.
=== MALLOC_CHECK_, aneb řešení ze standardní knihovny ===
 MALLOC_CHECK_
je systémová proměnná, pokud ji nastavíme na 2, program se zastaví pokud
* dojde k zápisu (ne přístupu !!) bezprostředně před začátek alokované paměti 
* dojde k dvojí dealokaci

Ukázka
 Popisuje program alokátor, který se pokusí dvakrát dealokovat alokovanou paměť
 $ export MALLOC_CHECK_=2
 $ ./alokator
 Aborted(core dumped)

=== mtrace ===
mtrace je výborný nástroj,ale má drobnou nevýhodu... program musí skončit normálně (tj. regulérním opuštěním funkce main), jinak tento nástroj nefunguje(nebo fungovat nemusí).
Jde použít na 
* detekci selhání dealokace alokované paměti
* detekci dealokace paměti, která nebyla alokována

Používá se násldujícím způsobem:
* Specifikujeme systémovou proměnnou 
 MALLOC_TRACE
* Do laděného kódu se vloží 
 #include <mcheck.h>
* Na začátku funkce main() se volá funkce mtrace
* No a ve specifikovaném souboru budou informace o chybných dealokacích
Ukázka
 $export MALLOC_TRACE=mlog.log
 $ ./alokator2
 $ cat mlog.log
 - 00000000000 Free 1 was never alloc'd alokator2.c:20
 Memory not freed
 ----------------
    Address    Size   Caller
 0x08049d48    0xc    at alokator2.c:15

=== knihovna electric fence ===
Tato knihovna zastaví program tam, kde dojde k přístupu mimo alokovanou paměť.
Detekuje to následující věci
* Zápis a čtení před a za alokovanou paměť
* Zápis a čtení do paměti, která byla dealokována
* Alokaci paměti nulové velikosti

Pracuje v podstatě tak, že do paměti nastřílí náhodnou špínu a program pak spadne hned a ne až jednou, za mnoho dní a nocí :-). Teda tohle se to traduje, ale ono  to dělá ještě i něco navíc. 
Na každý kousek paměti se alokují alespoň dvě stránky paměti paměť kterou jsme opravdu chtěli se umístí do konce první stránky, a knihovna hlídá jestli nechceme psát někam jinam do těch stránek.
Z toho ovšem plyne poměrně nepříjemná věc, že tahle kráska spotřebovává strašně moc paměti. Proto používat s mírou a pouze pro ladící účely. 
V každém případě, pokud uděláte něco z popsaných věcí tak to prostě spadne.

 $./alokator3
 Electric Fence 2.0.1 Copyright (C) 1987-1996 Brunce Perens.
 Segmentation fault

A kde to spadlo se můžeme podívat pomocí gdb
Docela pěkně jde kombinovat s gdb přímo, viz ta pasáž o konfiguráku.
Její použití spočívá v tom, že ji slinkujeme s programem
 gcc -c -Wall -pedantic alokator3.o -o alokator3 -lefence

=== valgrind ===
viz 
*http://www.valgrind.org/docs/manual/quick-start.html
*[[wen:Valgrind]]
*http://www.faqs.org/docs/Linux-HOWTO/Valgrind-HOWTO.html

[[Category:Referáty]]