Vývojári konzolových aplikácií v Jave to majú jednoduché. Ak zhlobia program, ktorý padne na chybovom stave, teda na výnimke, ktorú nik neodchytí, do konzoly sa im radostne vypíše stack trace, teda štrúdlik postupnosti volaní smerujúcej k riadku, na ktorom vznikla pôvodná chyba.
novotnyr@x64> java ThrowUp
Exception in thread "main" java.lang.NullPointerException
at ThrowUp.main(ThrowUp.java:4)
V tomto prípade vieme rovno otvoriť ThrowUp.java
, a zistiť, že na štvrtom riadku sa zjavne volá metóda na objekte, ktorý je v skutočnosti null
.
Ako na to v céčku? Nebolo by super, keby binárky v tomto jazyku po páde vypľuli niečo podobné ako stack trace?
V predošlom článku sme si ukázali mechanizmus ako to dosiahnuť. Skúsme to však rozšíriť použitím návratových kódov a trochou skriptovania v shelli.
Signály padnutých programov
Ak sa program pokúša pristúpiť k pamäti, ktorá nie je platná, Linux okamžite vyhodí signál č. 11. Neplatný prístup môže znamenať snahu prečítať neplatnú pamäť, napr. snahou pristúpiť k premennej na adrese 0 (obvykle v prípade, keď máme smerník na štruktúru, ktorý však ukazuje na NULL
), alebo snahu o zápis do pamäte, ktorá programu nepatrí.
Signály sa ideologicky podobajú na výnimky v objektovo-orientovaných programoch (v skutočnosti však vznikli omnoho skôr, dávno pred OOP). Operačný systém môže pomocou nich signalizovať programu výskyt špeciálnej situácie. Samotné signalizovanie prerušuje aktuálny beh programu. Niektoré signály môžu spôsobiť okamžité ukončenie behu programu, niektoré zase môžu zodpovedať zmenám v prostredí mimo programu, na ktoré môže program zareagovať rozumným spôsobom.
V unixovských operačných systémoch je každý signál jednoznačne identifikovaný číslom. Na linuxovských systémoch má prvých 32 signálov presne dohodnuté významy a vyššie čísla sú ponechané na jednotlivé programy.
kill
nevraždí, ale signalizuje
Typické použitie je v príkaze kill
. Ak chcete zabiť proces s PID 25, folklórom je
kill 25
V skutočnosti posiela kill
v štandardnom zápise príslušnému procesu signál SIGTERM
(číslo 15.)
Zoznam signálov a ich kódov na Debiane môžete získať výpisom
cat /usr/include/asm/signal.h | grep '^#define SIG'
Popis možno nájsť napr. na LinuxJournale.
Signály v bashi
Shelly majú známu vlastnosť: pomocou premennej $?
je možné zistiť exit code, teda kód stavu, s ktorým skončil naposledy spustený program. Dohoda hovorí, že nula zodpovedá stavu „zbehol som bez problémov“ a akékoľvek iné číslo indikuje chybu.
Táto dohoda sa odráža aj v céčkových programoch: číslo, ktoré vráti procedúra main()
sa použije ako návratový kód.
Shell bash
túto funkcionalitu obohacuje: ak program padol na neobslúženom signáli, návratový kód bude mať hodnotu 128 + číslo signálu.
Skúste si pustiť program, ktorý padá na Segmentation fault
:
novotnyr@x64> ./segfaultdemo
Segmentation fault
a spýtať sa na návratový kód
novotnyr@x64> echo $?
139
Segfault má kód 11, čiže 128 + 11 dáva 139.
Finálny skript
Finálny skript bude ideologicky nasledovný:
- nastavme
ulimit -c
na neobmedzenú hodnotu - skompilujme zdrojáky do binárky
- spustime binárku
- otestujme návratový kód: ak je 139, znamená to, že program segfaultol
- následne otestujme, či existuje core dump
- ak áno, tak ho spracujme
gdb
- a následne zmažme, aby nestrašil pri ďalšom behu
Hľa, výsledok:
#!/bin/sh
BINARY=main
SIGSEGV=139
COREDUMP=core
ulimit -c unlimited
gcc -Wall -Wextra -g -o $BINARY main.c modul.c
./$BINARY
if [ "$?" = "$SIGSEGV" ] ;
then
if [ -f $COREDUMP ] ;
then
gdb -batch $BINARY $COREDUMP
rm $COREDUMP
fi
fi
Názov binárky, číslo signálu a názov súboru s core dumpom je kvôli prehľadnosti vyhodený do premenných na začiatku skriptu.
Skript môžeme pomenovať run
, následne ho chmodnúť, aby sa dal spustiť
chmod +x ./run
A následný cyklus vývoja bude
./run
Typicky zlý beh bude vyzerať nasledovne:
novotnyr@x64> ./run
main.c: In function ‘main’:
main.c:12: warning: unused parameter ‘argc’
main.c:12: warning: unused parameter ‘argv’
Segmentation fault (core dumped)
warning: Can't read pathname for load map: Input/output error.
Core was generated by `./main'.
Program terminated with signal 11, Segmentation fault.
#0 0x0000000000400be3 in pridajKluc (sentinel=0x0, dlzka_databazy=-1,
nazovAktualnehoKluca=0x7fff08898c80 ";",
nazovAktualnejHodnotyKluca=0x7fff08898cb0 "\300\214\211\b\377\177",
aktualnyListKomentarov=0x6f70b0) at modul.c:111
111 new_kluc->next = sentinel->next;