web-dev-qa-db-ja.com

SIGSEGVをキャッチするシグナルハンドラを記述する方法

SIGSEGVをキャッチするシグナルハンドラを作成します。読み取りまたは書き込みを使用してメモリブロックを保護します

char *buffer;
char *p;
char a;
int pagesize = 4096;

mprotect(buffer,pagesize,PROT_NONE)

これにより、バッファから始まるページサイズのメモリが読み取りまたは書き込みから保護されます。

次に、メモリを読み取ろうとします。

p = buffer;
a = *p 

これによりSIGSEGVが生成され、ハンドラーが呼び出されます。ここまでは順調ですね。私の問題は、ハンドラが呼び出されたら、次のようにしてメモリのアクセス書き込みを変更することです

mprotect(buffer,pagesize,PROT_READ);

そして、私のコードの通常の機能を続けます。関数を終了したくありません。同じメモリへの今後の書き込みで、信号を再度キャッチし、書き込み権を変更してから、そのイベントを記録したいと思います。

コード

#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

char *buffer;
int flag=0;

static void handler(int sig, siginfo_t *si, void *unused)
{
    printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr);
    printf("Implements the handler only\n");
    flag=1;
    //exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    char *p; char a;
    int pagesize;
    struct sigaction sa;

    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1)
        handle_error("sigaction");

    pagesize=4096;

    /* Allocate a buffer aligned on a page boundary;
       initial protection is PROT_READ | PROT_WRITE */

    buffer = memalign(pagesize, 4 * pagesize);
    if (buffer == NULL)
        handle_error("memalign");

    printf("Start of region:        0x%lx\n", (long) buffer);
    printf("Start of region:        0x%lx\n", (long) buffer+pagesize);
    printf("Start of region:        0x%lx\n", (long) buffer+2*pagesize);
    printf("Start of region:        0x%lx\n", (long) buffer+3*pagesize);
    //if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1)
    if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1)
        handle_error("mprotect");

    //for (p = buffer ; ; )
    if(flag==0)
    {
        p = buffer+pagesize/2;
        printf("It comes here before reading memory\n");
        a = *p; //trying to read the memory
        printf("It comes here after reading memory\n");
    }
    else
    {
        if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1)
        handle_error("mprotect");
        a = *p;
        printf("Now i can read the memory\n");

    }
/*  for (p = buffer;p<=buffer+4*pagesize ;p++ ) 
    {
        //a = *(p);
        *(p) = 'a';
        printf("Writing at address %p\n",p);

    }*/

    printf("Loop completed\n");     /* Should never happen */
    exit(EXIT_SUCCESS);
}

問題は、シグナルハンドラーのみが実行され、シグナルをキャッチした後にメイン関数に戻ることができないことです。

68
Adi

シグナルハンドラーが戻ると(exit、longjmp、または実際に戻ることを妨げるものを呼び出さないと仮定して)、シグナルが発生したポイントでコードが続行され、同じ命令が再実行されます。この時点では、メモリ保護は変更されていないため、再びシグナルがスローされるだけで、シグナルハンドラーは無限ループに戻ります。

したがって、機能させるには、シグナルハンドラでmprotectを呼び出す必要があります。残念ながら、Steven Schanskerが指摘しているように、mprotectは非同期に対して安全ではないため、シグナルハンドラから安全に呼び出すことはできません。したがって、POSIXに関する限り、あなたはうんざりしています。

幸いなことに、ほとんどの実装(私が知っている限りではすべての最新のUNIXおよびLinuxのバリアント)では、mprotectはシステムコールなので、 シグナルハンドラー内から呼び出しても安全です です。欲しいです。問題は、読み取り後に保護を元に戻す場合、読み取り後にメインプログラムで保護を変更する必要があることです。

もう1つの可能性は、シグナルハンドラの3番目の引数で何かを行うことです。このハンドラは、シグナルが発生した場所に関する情報を含むOSおよびArch固有の構造を指します。 Linuxでは、これはucontext構造体であり、$ PCアドレスおよび信号が発生したその他のレジスタの内容に関するマシン固有の情報が含まれています。これを変更すると、シグナルハンドラーの戻り先が変更されるため、$ PCをエラーが発生した命令の直後に変更して、ハンドラーが戻った後に再実行されないようにすることができます。これを正しく行うには非常に注意が必要です(また、移植性もありません)。

編集

ucontext構造体は、<ucontext.h>で定義されています。 ucontext内のuc_mcontextフィールドにはマシンコンテキストが含まれ、that内の配列gregsには汎用レジスタコンテキストが含まれます。あなたのシグナルハンドラーで:

ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];

例外が発生したPCを提供します。それを読んで、どの命令が失敗したのかを理解し、別のことを行うことができます。

シグナルハンドラでmprotectを呼び出すことの移植性に関する限り、SVID仕様またはBSD4仕様のいずれかに準拠するシステムは安全である必要があります-シグナルでシステムコール(マニュアルのセクション2にあるもの)を呼び出すことができます。ハンドラ。

67
Chris Dodd

すべての人が最初に信号を処理しようとするときに行うトラップに陥りました。トラップ?シグナルハンドラーで実際に何でも役に立つできると考えています。シグナルハンドラからは、非同期でリエントラントセーフなライブラリ呼び出しのみを呼び出すことができます。

このCERTアドバイザリ の理由と、安全なPOSIX関数のリストを参照してください。

既に呼び出しているprintf()はそのリストにないことに注意してください。

Mprotectもありません。シグナルハンドラから呼び出すことはできません。それはmight動作しますが、将来問題が発生することを約束できます。シグナルハンドラーには細心の注意を払ってください。正しく処理するには注意が必要です。

[〜#〜] edit [〜#〜]

私はすでに移植性のダッチバッグになっているので、適切な予防策を講じずに 共有(つまりグローバル)変数に書き込むべきではない であることを指摘します。

24

LinuxのSIGSEGVから回復できます。また、Windowsでセグメンテーションフォールトから回復することもできます(シグナルの代わりに構造化例外が表示されます)。しかし、 POSIX規格は回復を保証しません なので、コードは非常に移植性が低くなります。

libsigsegv を見てください。

12
Ben Voigt

振る舞いは未定義なので、シグナルハンドラから戻るべきではありません。むしろ、longjmpで飛び出します。

これは、async-signal-safe関数で信号が生成される場合にのみ問題ありません。それ以外の場合、プログラムが別のasync-signal-unsafe関数を呼び出す場合の動作は未定義です。したがって、シグナルハンドラは必要になる直前にのみ確立し、できるだけ早く解除する必要があります。

実際、SIGSEGVハンドラーの使用例はほとんどありません。

  • async-signal-safeバックトレースライブラリを使用してバックトレースをログに記録してから死にます。
  • jVMまたはCLRなどのVM:JITでコンパイルされたコードでSIGSEGVが発生したかどうかを確認します。発生していない場合は死にます。発生した場合は、言語固有の例外(notC++例外)。これは、JITコンパイラーがトラップが発生する可能性があることを認識しており、適切なフレームアンワインドデータを生成したためです。
  • clone()およびexec()デバッガー(donotはfork()を使用します。これはpthread_atfork()によって登録されたコールバックを呼び出します)。

最後に、SIGSEGVをトリガーするアクションは、おそらく無効なメモリにアクセスしているため、UBであることに注意してください。ただし、信号がたとえばSIGFPEの場合、これは当てはまりません。

4
Demi

ucontext_tまたはstruct ucontext/usr/include/sys/ucontext.hに存在)を使用したコンパイルの問題があります

http://www.mail-archive.com/[email protected]/msg13853.html

0
shreshtha