7

SIGSEGVマルチスレッド環境でシグナルをキャッチすることが可能かどうか、または推奨される方法を知りたいです。SIGSEGVのようなものによって発生したを処理することに特に興味があり*((int *)0) = 0ます。

このトピックを読んだ結果、シグナル ハンドラをインストールするsignal()とにたどり着きました。sigaction()どちらもマルチスレッド環境では有望とは思えませんが。次に、他のスレッドのシグナルをブロックsigwaitinfo()する前のpthread_sigmask()呼び出しで、1 つのスレッドでシグナルを受信して​​みました。SIGSEGVスレッド内で raise() を使用してシグナルが発生した範囲、またはkill -SIGSEGV;のようなものによってプロセスに送信されたときに機能しました。ただし、\*((int*)0) = 0それでもプロセスを強制終了します。私のテストプログラムは次のとおりです

void block_signal()
{
        sigset_t set;

        sigemptyset(&set);
        sigaddset(&set, SIGSEGV);
        sigprocmask(SIG_BLOCK, &set, NULL);

        if (pthread_sigmask(SIG_BLOCK, &set, NULL)) {
                fprintf(stderr, "pthread_sigmask failed\n");
                exit(EXIT_FAILURE);
        }
    }

void *buggy_thread(void *param)
{
        char *ptr = NULL;
        block_signal();

        printf("Thread %lu created\n", pthread_self());

        // Sleep for some random time
        { ... }

        printf("About to raise from %lu\n", pthread_self());

        // Raise a SIGSEGV
        *ptr = 0;

        pthread_exit(NULL);
}

void *dispatcher(void *param)
{
        sigset_t set;
        siginfo_t info;
        int sig;

        sigemptyset(&set);
        sigaddset(&set, SIGSEGV);

        for (;;) {
                sig = sigwaitinfo(&set, &info);
                if (sig == -1)
                        fprintf(stderr, "sigwaitinfo failed\n");
                else
                        printf("Received signal SIGSEGV from %u\n", info.si_pid);
        }
}

int main()
{
        int i;
        pthread_t tid;
        pthread_t disp_tid;

        block_signal();

        if (pthread_create(&disp_tid, NULL, dispatcher, NULL)) {
                fprintf(stderr, "Cannot create dispatcher\n");
                exit(EXIT_FAILURE);
        }

        for (i = 0; i < 10; ++i) {
                if (pthread_create(&tid, NULL, buggy_thread, NULL) {
                        fprintf(stderr, "Cannot create thread\n");
                        exit(EXIT_FAILURE);
                }
        }

        pause();
}

予期せず、プログラムはレイザーのスレッド ID を出力する代わりにセグメンテーション違反で終了します。

4

3 に答える 3

10

あなたのコードはsigaction(2)を呼び出していません。私はそれを呼び出すべきだと思います。signal(7)およびsignal-safety(7)も参照してください。そして、シグナルアクション(フィールドを介して、問題のあるマシン命令をスキップするか、問題のあるアドレスに移動するか、呼び出しますsa_sigaction)で何か(マシン固有)を行う必要があります。そうしないと、信号ハンドラーから戻るときに、問題のあるマシン命令再起動されます。siginfo_tmmapsiglongjmpSIGSEGV

SIGSEGV同期シグナル(やなど)はスレッド固有であるため(この回答を参照)、別のスレッドでSIGSEGV処理SIGSYSすることはできません。そのため、達成しようとすることは機能しsigwaitinfoません。特にSIGSEGV、問題のあるスレッドに向けられています

Linux シグナルについてもすべてお読みください。

PS。巧妙な処理の例は、SIGSEGV(2019 年 5 月に) もうメンテナンスされていないRavenbrook MPSガベージ コレクター ライブラリによって提供されます。Linux 固有の最近のuserfaultfd(2)およびsignalfd(2)システム コールにも注意してください。

于 2013-04-25T00:55:14.723 に答える
8

メモリ アクセスの障害によって発生したシグナル配信はSIGSEGV、無効なアクセスを実行したスレッドに対して行われます。POSIX ごと ( XSH 2.4.1 ):

生成時に、シグナルがプロセスに対して生成されたのか、プロセス内の特定のスレッドに対して生成されたのかを判断する必要があります。ハードウェア障害など、特定のスレッドに起因するアクションによって生成されるシグナルは、シグナルを生成させたスレッドに対して生成されます。プロセス ID またはプロセス グループ ID、または端末アクティビティなどの非同期イベントに関連して生成されるシグナルは、プロセスに対して生成されます。

SIGSEGVマルチスレッド プログラムで処理しようとする場合の問題点は、配信とシグナル マスクがスレッド ローカルであるのに対し、シグナル処理(つまり、どのハンドラを呼び出すか) がプロセス グローバルであるということです。つまり、sigaction呼び出しスレッドだけでなく、プロセス全体のシグナル ハンドラーを設定します。これは、複数のスレッドがそれぞれ独自のSIGSEGVハンドラーをセットアップしようとすると、互いの設定が破壊されることを意味します。

SIGSEGV私が提案できる最善の解決策は、を使用するためのグローバル シグナル ハンドラを設定することですsigaction。できれSA_SIGINFOば、障害に関する追加情報を取得してから、特定のスレッドのハンドラにスレッド ローカル変数を設定します。次に、実際のシグナルハンドラーは次のようになります。

_Thread_local void (*thread_local_sigsegv_handler)(int, siginfo_t *, void *);
static void sigsegv_handler(int sig, siginfo_t *si, void *ctx)
{
    thread_local_sigsegv_handler(sig, si, ctx);
}

これは C11 スレッド ローカル ストレージを使用することに注意してください。それが利用できない場合は、「GNU C」__threadスレッドローカルストレージまたは POSIX スレッド固有のデータ (pthread_key_createおよびpthread_setspecific/を使用pthread_getspecific) にフォールバックできます。厳密に言えば、後者は非同期シグナル セーフではないため、標準ライブラリの非同期シグナル セーフでない関数内で不正なアクセスが発生した場合、シグナル ハンドラーからそれらを呼び出すと UB が呼び出されます。ただし、それが独自のコードで発生した場合、非同期シグナルセーフではない関数がシグナルハンドラーによって中断されなかったことを確認できます。したがって、これらの関数は明確に定義された動作をします (つまり、プログラム全体のモジュロおそらく、生成するために何をしたとしても、すでにUBを持っていSIGSEGVます...)。

于 2013-04-25T01:55:52.310 に答える
2

「なんでSIGSEGVを捕まえたいの?捕まえた後どうするの?」

最も一般的な答えは、quit/abort です。しかし、では、プロセスを任意に終了させる代わりに、このシグナルをプロセスに配信する理由は何でしょうか?

答えは: SIGSEGV を含むシグナルは単なる例外であり、一部のアプリケーションではハードウェア出力を「セーフ モード」に設定するか、プロセスを終了する前に重要なデータが一貫した状態のままであることを確認することが非常に重要であるためです。 .

一般に、2 種類の segfaults があります。書き込みまたは読み取り操作が原因です。

読み取り操作によって引き起こされるセグメンテーション違反は、完全に安全にキャッチでき、場合によっては無視することさえできます(1)。失敗した書き込み操作を安全に処理するには、より多くの注意と努力が必要ですが (データ/メモリの破損のリスク)、これも可能です (セグメンテーション違反の後にメモリを動的に割り当てることを回避することにより)。

「重要なシグナル」(SIGFPE や SIGSEGV などの特定のスレッドに配信される) の問題は、通常、プログラムがシグナルのコンテキストが何であるかを「認識」していないことです。信号。

これらの情報を取得するには、少なくともいくつかの方法があります。たとえば、次のようになります。

  1. 各スレッドは小さな操作の 1 つのクラスのみを実行できます。そのため、シグナルを受信した場合、何が起こったかを簡単に知ることができます -> スレッドを終了し、処理されたデータを確認するなど -> 安全に終了します。
  2. C 例外を使用する- すぐに使用できるソリューションはほとんどありません。私のものは次のとおりです: libcxc

(1) ESRCH と pthread_kill() の有名な問題は、すでに独自に終了したスレッドに対して発行されます :)

于 2016-09-17T21:39:06.110 に答える