GNU/Linux >> Znalost Linux >  >> Linux

Jak napsat obsluhu signálu pro zachycení SIGSEGV?

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í.


Linux
  1. Jak napsat text na obrázek pomocí příkazu Linux

  2. Jak zapsat soubor do jiného?

  3. Jak signalizovat konec vstupu Stdin?

  1. Jak se připojit k vláknu, které visí na blokování IO?

  2. Jak zapíšu znaky jiné než ASCII pomocí echa?

  3. Zařazení signálu do fronty v C

  1. Jak napsat smyčku v Bash

  2. Jak vyřešit:Nelze zapisovat do oddílu Ext3 nebo Ext4

  3. Jak se vyhnout použití printf v obslužném programu signálu?