Ak začínate s programovaním v C, viete stráviť mnoho hodín či dní pasívnym pozeraním do monitora a hľadaním logických chýb. Veď s alokáciou pamäte a zlým prístupom je kopa srandy. O to viac na Windowse, kde sa neraz musíte uchýliť k telepatii. Na cvičení z Programovacieho jazyka C sme používali primárne NetBeans v kombinácii s MingW. Samotné prostredie je výborné, kým veci fungujú, všetko je v poriadku, lenže v mnohých prípadoch vám NetBeans vypíše do konzoly prosté:
RUN FAILED
A to je všetko. Včerajší program bol v princípe jednoduchý, lenže v niektorej z piatich procedúr pracujúcich nad spojovým zoznamom bol problém s alokáciou. Tradičná kombinácia
gcc -Wall -Wextra -ansi -pedantic
ktorou viete vychytať nejednu záludnú chybu, neurobila nič. Teda pardón:
RUN FAILED
Frustrácia je o to väčšia, že ani lint
, teda pozorný kontrolér kadejakých chýb nevypísal medzi prúdom varovaní a chýb nič, o čo by sa dalo oprieť. A možno vypísal, len si to štyri páry očí nevšimli. Nehovoriac o tom, že v rýchlosti sme nezistili, či existuje nejaká lintovská binárka a verzia splint
je k dispozícii len vtedy, ak používate namiesto MingW celú sadu CygWin (ktorá má niekedy tiež pozoruhodnú sadu vlastných problémov). To, že v netbeansáckej konzole program failne a nevypíše nič ani v prípadoch, keď jeho spustenie na Linuxe vedie k aspoň čiastočným výpisom na konzolu, radšej nehovorím. RUN FAILED
. Nič viac a nič menej.
Linux to the rescue
Nakoniec pomohol vzdialený linuxový server. Prihlásenie cez putty
, nakopírovanie súborov na server cez WinSCP
a použitie pestrej sady nástrojov vyriešilo problém. Stačilo presvedčiť operačný systém, aby dumpoval core (vysypal jadro ;-)), teda vypísal do súboru obsah pamäte, s ktorou narábal spustený program vo chvíli, keď zomrel v strašných kŕčoch. Následne sa dal použiť gdb
, teda Gnu Debugger, ktorý dokáže zanalyzovať core dump a vypísať z neho konkrétny riadok, na ktorom program zdochol.
Samotný program pozostával z troch súborov:
main.c
je hlavný program riešiaci používateľské rozhranieparser.c
je knižnica, ktorá rieši spracovávanie istého typu súborov (nie je dôležité akých) a je volaná z hlavného programuparser.h
je hlavičkový súbor knižnice parsera
Základná kompilácia je jednoduchá
gcc main.c parser.c
Samozrejme, toto neobsahuje tradičné varovania a nekontroluje kompatibilitu s ANSI C a výstupom je binárka s kreatívnym názvom a.out
. Medzirečou pripomeňme, že gcc
automaticky zistí, že hlavný súbor potrebuje knižnicu parsera, nájde jej hlavičkový súbor a všetko prekompiluje v správnom poradí.
Spustenie divného programu vypľuje nemenej poetickú hlášku ako na Windowse:
novotnyr@student> ./a.out
Segmentation fault
Z dažďa pod odkvap.
Čo potrebujeme spraviť?
- presvedčiť operačný systém, aby dumpoval core
- použiť
gdb
- a ešte predtým presvedčiť kompilátor, aby do binárky zakompiloval ladiace údaje, o. i. čísla riadkov pre jednotlivé inštrukcie, pretože bez nich toho veľa nezistíme
Zapnutie core dumpu
Distribúcia Debianu v štandardnom režime nedumpuje core padnutých programov. Prečo? Lebo ulimit -c
je nula. Samotný ulimit
je linuxu vlastný mechanizmus reštrikcie ohraničení pre používateľské programy. Pomocou neho možno ohraničiť maximálny počet otvorených súborov, maximálny počet spustených procesov a podobne. Jednoducho ochranný mechanizmus proti zákerným či zlým programom. Obveseľte sa dostupnými limitmi:
ulimit -a
V tejto chvíli je najzaujímavejší limit -c
, ktorý udáva veľkosť súboru core dumpu, ktorý je … tá nula.
Dočasne (ak ste prihlásený cez putty
, tak počas trvania sedenia) je ju možné zmeniť na nekonečne veľa cez
ulimit -c unlimited
Kompilácia
Kompilácia bude jemne komplikovanejšia:
gcc -Wall -Wextra -ansi -pedantic -o main -g main.c parser.c
Parametre k wallom, wextrám, ansom a pedanticom sú známe. -o
vyšpecifikuje lepší názov binárky (main
), parameter -g
(G ako debuG) zapne kompiláciu ladiacich informácií a zvyšok je zrejmý.
main
, pýcha a pád
Ak si pustíte svoj krásny, ale zlý program
./main
dožijete tradičného unixáckeho
Segmentation fault (core dumped)
Všimnite si bonus… core dumped
— v adresári sa zjaví samotná vykládka jadra pod názvom core
(prečo sa to volá jadro, nepýtajte sa. S jadrom to veľa spoločného nemá, je to historický názov).
Podajte mi jeden debugger
Je čas na debugger. gdb
dokáže zobrať vašu binárku, súbor s dumpom a vypísať príčinu
gdb a.out core
Debugger vás oblaží mnohými informačnými hláškami, z ktorých vás zaujíma až finále
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000400be3 in pridajKluc (sentinel=0x0, dlzka_databazy=-1,
nazovAktualnehoKluca=0x7fff32ff8b30 ";",
nazovAktualnejHodnotyKluca=0x7fff32ff8b60 "p\213\377\062\377\177",
aktualnyListKomentarov=0x15e80b0) at modul.c:111
111 new_kluc->next = sentinel->next;
Hľa, páchateľ! Riadok 111 a práca so smerníkmi. Všimnime si radu v prvom riadku: sentinel=0x0
, čo znamená, že smerník sentinel
ukazuje na NULL
, lenže následne chceme pristúpiť k sentinel->next
, čo je somarina. V Jave by sme dostali NullPointerException
, v céčku krásny segfault.
Sme na konci, vinník je odhalený, môžeme opraviť a pokračovať v práci!
Ako na Windowse?
Nijak. Vlastnosť core dump
u je záležitosť operačného systému a padnuté programy na Windowse dump neprodukujú. Ak si váš program spustíte z príkazového riadku, zistíte, že
> Windows zistil, že program prestal pracovať. Hľadá sa riešenie...
Windows zrejme volat domů a možno nájde v Microsofte odpoveď na otázku, prečo váš zápočťák nefunguje.