75

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);
}

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

4

5 に答える 5

75

シグナル ハンドラーが戻ると (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 にあるもの) を呼び出すことができます。ハンドラ。

于 2010-04-18T19:17:35.260 に答える
24

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

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

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

mprotect もありません。シグナルハンドラから呼び出すことはできません。うまくいくかもしれませんが、今後問題が発生することを約束できます。シグナルハンドラには十分注意してください。正しく処理するのは難しいです!

編集

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

于 2010-04-18T18:59:49.480 に答える
13

Linux では SIGSEGV から復旧できます。また、Windows のセグメンテーション違反から回復することもできます (シグナルの代わりに構造化された例外が表示されます)。しかし、POSIX 標準では回復が保証されていないため、コードの移植性は非常に低くなります。

libsigsegvを見てください。

于 2010-04-18T19:20:24.257 に答える
5

シグナル ハンドラーから戻らないでください。その場合の動作は未定義です。むしろ、longjmp で飛び出してください。

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

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

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

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

于 2013-06-18T23:55:17.477 に答える
0

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

http://www.mail-archive.com/arch-general@archlinux.org/msg13853.html

于 2010-08-12T04:48:40.693 に答える