Můžete se zotavit ze SIGSEGV na linuxu. Také se můžete zotavit z chyb segmentace ve Windows (místo signálu uvidíte strukturovanou výjimku). Ale standard POSIX nezaručuje obnovu, takže váš kód bude velmi nepřenosný.
Podívejte se na libsigsegv.
Když se váš obslužný program signálu vrátí (za předpokladu, že nevolá exit nebo longjmp nebo něco, co mu brání ve skutečném návratu), kód bude pokračovat v místě, kde se signál objevil, a znovu provede stejnou instrukci. Vzhledem k tomu, že v tomto okamžiku nebyla ochrana paměti změněna, pouze znovu spustí signál a vy se vrátíte do svého ovladače signálu v nekonečné smyčce.
Takže aby to fungovalo, musíte zavolat mprotect v obslužném programu signálu. Bohužel, jak poznamenává Steven Schansker, mprotect není asynchronně bezpečný, takže jej nemůžete bezpečně zavolat z obsluhy signálu. Takže, pokud jde o POSIX, jste v háji.
Naštěstí na většině implementací (pokud vím všechny moderní UNIXové a Linuxové varianty), mprotect je systémové volání, takže je bezpečné volat z obslužného programu signálu, takže můžete dělat většinu toho, co chcete. Problém je v tom, že pokud chcete po čtení změnit ochrany zpět, budete to muset udělat v hlavním programu po čtení.
Další možností je udělat něco se třetím argumentem pro obsluhu signálu, který ukazuje na specifickou strukturu OS a oblouku, která obsahuje informace o tom, kde se signál vyskytl. V systému Linux se jedná o ukontext struktura, která obsahuje informace specifické pro stroj o adrese $PC a dalším obsahu registru, kde se signál vyskytl. Pokud toto upravíte, změníte, kam se bude obsluha signálu vracet, takže můžete změnit $PC tak, aby bylo hned po chybné instrukci, takže se znovu nespustí po návratu handleru. To je velmi složité na správné (a také nepřenosné).
upravit
ucontext
struktura je definována v <ucontext.h>
. V rámci ucontext
pole uc_mcontext
obsahuje kontext stroje a v rámci toho , pole gregs
obsahuje obecný kontext registru. Takže ve vašem obslužném programu signálu:
ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];
vám dá počítač, kde došlo k výjimce. Můžete si ji přečíst, abyste zjistili, která instrukce byla chybná, a udělat něco jiného.
Pokud jde o přenositelnost volání mprotect v obslužné rutině signálu, každý systém, který se řídí buď specifikací SVID nebo specifikací BSD4, by měl být bezpečný – umožňuje volání jakéhokoli systémového volání (cokoli v sekci 2 manuálu) v signálu. handler.
Dostali jste se do pasti, kterou dělají všichni lidé, když se poprvé snaží zvládnout signály. Past? Myslete na to, že vlastně můžete dělat cokoli užitečného se signálními ovladači. Z obslužného programu signálů můžete volat pouze asynchronní a reentrant-safe volání knihovny.
Podívejte se na toto doporučení CERT, proč a na seznam funkcí POSIX, které jsou bezpečné.
Všimněte si, že printf(), které již voláte, není na tomto seznamu.
Ani mprotect. Nesmíte to volat od obsluhy signálů. Mohlo by práce, ale můžu vám slíbit, že se po cestě dostanete do problémů. Buďte opravdu opatrní s manipulátory signálů, je těžké se dostat správně!
UPRAVIT
Vzhledem k tomu, že jsem v tuto chvíli již šmejd v oblasti přenositelnosti, zdůrazňuji, že byste také neměli zapisovat do sdílených (tj. globálních) proměnných, aniž byste přijali patřičná opatření.