Linux では SIGSEGV から復旧できます。また、Windows のセグメンテーション違反から回復することもできます (シグナルの代わりに構造化された例外が表示されます)。ただし、POSIX 標準では回復が保証されていないため、コードの移植性は非常に低くなります。
libsigsegv を見てください。
シグナル ハンドラーが戻ると (exit や longjmp、または実際に戻るのを妨げる何かを呼び出していないと仮定すると)、コードはシグナルが発生した時点で続行し、同じ命令を再実行します。この時点では、メモリ保護は変更されていないため、シグナルが再度スローされるだけで、無限ループでシグナル ハンドラーに戻ります。
したがって、これを機能させるには、シグナル ハンドラで mprotect を呼び出す必要があります。残念ながら、Steven Schansker が指摘しているように、mprotect は非同期セーフではないため、シグナル ハンドラから安全に呼び出すことはできません。したがって、POSIX に関する限り、あなたはめちゃくちゃです。
幸いなことに、ほとんどの実装 (私の知る限り、すべての最新の UNIX および Linux バリアント) では、mprotect はシステム コールであるため、シグナル ハンドラー内から安全に呼び出すことができるため、必要なことのほとんどを行うことができます。問題は、読み取り後に保護を元に戻したい場合、読み取り後にメイン プログラムでそれを行う必要があることです。
もう 1 つの可能性は、シグナル ハンドラーへの 3 番目の引数で何かを行うことです。これは、シグナルが発生した場所に関する情報を含む OS およびアーキテクチャ固有の構造を指します。 Linux では、これは ucontext です。 この構造体には、シグナルが発生した $PC アドレスとその他のレジスタの内容に関するマシン固有の情報が含まれています。これを変更すると、シグナル ハンドラーが戻る場所が変更されるため、$PC をエラーのある命令の直後に変更して、ハンドラーが戻った後に再実行されないようにすることができます。これを正しく行うのは非常に難しいです (そして移植性もありません)。
編集
ucontext
構造体は <ucontext.h>
で定義されています . ucontext
内 フィールド uc_mcontext
マシン コンテキストを含み、その内に 、配列 gregs
汎用レジスタ コンテキストが含まれます。したがって、シグナルハンドラーで:
ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];
例外が発生した PC が表示されます。それを読んで、どの命令が失敗したかを突き止め、別のことを行うことができます。
シグナル ハンドラで mprotect を呼び出す移植性に関する限り、SVID 仕様または BSD4 仕様のいずれかに準拠するシステムは安全である必要があります。これらのシステムでは、シグナル内で任意のシステム コール (マニュアルのセクション 2 にあるもの) を呼び出すことができます。
あなたは、すべての人が最初に信号を処理しようとするときに陥る罠に陥っています。トラップ? 役立つことなら何でもできると思っている シグナルハンドラで。シグナル ハンドラーからは、非同期でリエントラント セーフなライブラリ呼び出しのみを呼び出すことができます。
安全な POSIX 関数の理由とリストについては、この CERT 勧告を参照してください。
既に呼び出している printf() はそのリストにないことに注意してください。
mprotect もありません。シグナルハンドラから呼び出すことはできません。 かもしれない 動作しますが、今後問題が発生することを約束できます。シグナル ハンドラーには十分注意してください。正しく処理するのは難しいです!
編集
私は現時点ですでに移植性にうるさいので、適切な予防策を講じずに共有 (つまりグローバル) 変数に書き込むべきではないことを指摘します。