Turbosprievodca ladením padnutých programov

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é rozhranie
  • parser.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 programu
  • parser.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 dumpu 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.