信号をキャッチしてユーザーに問題を報告するポータブルな方法
-
01-07-2019 - |
質問
奇跡的にプログラムでセグメンテーション違反が発生した場合、SIGSEGV をキャッチして、単一のリターン コードでユーザー (おそらく GUI クライアント) に重大な問題が発生したことを知らせたいと考えています。同時に、どのシグナルがキャッチされたかを示す情報をコマンドラインに表示したいと考えています。
現在、シグナル ハンドラーは次のようになります。
void catchSignal (int reason) {
std :: cerr << "Caught a signal: " << reason << std::endl;
exit (1);
}
これを読むと、上記のような恐怖の叫び声が聞こえます。 糸 シグナル ハンドラーから非リエントラント関数を呼び出すのは悪であるということです。
信号を処理してユーザーに情報を提供するポータブルな方法はありますか?
編集: それとも、少なくとも POSIX フレームワーク内で移植可能でしょうか?
解決
これ テーブル に、POSIX が非同期シグナルセーフであることを保証し、シグナル ハンドラーから呼び出すことができるすべての関数をリストします。
この表の「write」コマンドを使用すると、次の比較的「醜い」解決策がうまくいけばうまくいきます。
#include <csignal>
#ifdef _WINDOWS_
#define _exit _Exit
#else
#include <unistd.h>
#endif
#define PRINT_SIGNAL(X) case X: \
write (STDERR_FILENO, #X ")\n" , sizeof(#X ")\n")-1); \
break;
void catchSignal (int reason) {
char s[] = "Caught signal: (";
write (STDERR_FILENO, s, sizeof(s) - 1);
switch (reason)
{
// These are the handlers that we catch
PRINT_SIGNAL(SIGUSR1);
PRINT_SIGNAL(SIGHUP);
PRINT_SIGNAL(SIGINT);
PRINT_SIGNAL(SIGQUIT);
PRINT_SIGNAL(SIGABRT);
PRINT_SIGNAL(SIGILL);
PRINT_SIGNAL(SIGFPE);
PRINT_SIGNAL(SIGBUS);
PRINT_SIGNAL(SIGSEGV);
PRINT_SIGNAL(SIGTERM);
}
_Exit (1); // 'exit' is not async-signal-safe
}
編集: 窓の上に建物を建てる。
このウィンドウをビルドしようとすると、「STDERR_FILENO」が定義されていないようです。ただし、ドキュメントによると、その値は「2」であるようです。
#include <io.h>
#define STDIO_FILENO 2
編集: 「exit」もシグナル ハンドラーから呼び出すべきではありません。
が指摘したように、 フィザー, 、上記の _Exit の呼び出しは、HUP や TERM などのシグナルに対する大ハンマーのアプローチです。理想的には、これらのシグナルが捕捉されたときに、「volatile sig_atomic_t」タイプのフラグを使用して、メイン プログラムに終了する必要があることを通知できます。
検索で役に立ったのは次のとおりです。
他のヒント
FWIW, 2 は Windows でも標準エラーですが、write() が _write() と呼ばれるため、条件付きコンパイルが必要になります。あなたも欲しくなるでしょう
#ifdef SIGUSR1 /* or whatever */
C 標準で定義されていることが保証されていない信号へのすべての参照についてなど。
また、上で述べたように、SIGUSR1、SIGHUP、SIGINT、SIGQUIT、および SIGTERM をこのように処理する必要はありません。
リチャード、まだコメントするにはカルマが足りないので、新しい答えになるのではないかと思います。これらは非同期信号です。いつ配信されるかわからないため、おそらく、一貫性を保つために完了する必要があるライブラリ コードが使用されることになります。したがって、これらのシグナルのシグナル ハンドラーは返す必要があります。exit() を呼び出すと、ライブラリは、atexit() で登録された関数の呼び出しや標準ストリームのクリーンアップなど、main() 後にいくつかの作業を実行します。たとえば、信号が標準ライブラリ I/O 関数に到着した場合、この処理は失敗する可能性があります。したがって、C90 では exit() を呼び出すことはできません。C99 では、stdlib.h に新しい関数 _Exit() を提供することで要件が緩和されていることが分かりました。_Exit() は、非同期信号のハンドラーから安全に呼び出すことができます。_Exit() は atexit() 関数を呼び出さず、実装の裁量で標準ストリームのクリーンアップを省略する場合があります。
bk1e へ (いくつかの投稿にコメントしてください)SIGSEGV が同期であるという事実が、再入可能に設計されていない関数を使用できない理由です。クラッシュした関数がロックを保持していて、シグナル ハンドラーによって呼び出された関数が同じロックを取得しようとした場合はどうなるでしょうか?
これは可能性ですが、問題は「SIGSEGV が同期であるという事実」ではありません。非同期シグナルの場合、ハンドラーから非再入可能関数を呼び出すことは、次の 2 つの理由により非常に困難です。
- 非同期信号ハンドラーは、(一般的に)通常のプログラムの実行を返して再開することを望んでいます。同期信号のハンドラーは、とにかく(一般的に)終了することです。そのため、クラッシュしてもあまり失われません。
- ひねくれた意味では、同期信号がいつ配信されるかを完全に制御できます。同期信号は、欠陥のあるコードを実行するときに発生し、それ以外のときには発生しません。非同期信号がいつ配信されるかについては、まったく制御できません。OP 自身の I/O コード自体が欠陥の原因である場合を除きます。不正な char* を出力しています - このエラー メッセージは成功する可能性がかなりあります。
プログラムを実行し、異常な終了コードをユーザーに報告するランチャー プログラムを作成します。