ごくたまにGDB上で再現しないバグに遭遇することがある。
自分の場合は並列バグが問題で、gdb上だとタイミングが変わってしまいバグが再現しなくなった。
なので、gdbを介さずにプログラムを普通に走らせておき、セグフォが発生した時点でgdbを起動して検死できるようにしたい。
アイデアとしては、プログラム自体にシグナルハンドラを仕込んでおき、SIGSEGV
が発生した時点でsleep
を呼んで停止、あとは手動でgdb attach
をかける。
手順
1. SIGSEGV
を捕捉するシグナルハンドラをプログラムに追加
まずはシグナルハンドラを定義。後からgdbでattachできるように十分な時間sleepするようにする。
void segv_handler(int sig) {
sleep(100000000);
}
次にsigaction()
コールを使ってシグナルハンドラを登録しておく。
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = segv_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
2. セグフォが起きるまで待つ
3. gdbでattachする
セグフォが発生してsleepし始めたら適当にpidを調べ、
$ gdb attach <pid>
でattach。これでセグフォをgdbで追跡可能。
サンプルプログラム
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void segv_handler(int sig) {
/* printf() cannot be used because it's not async-signal-safe */
write(2, "segfault\n", 9);
sleep(100000000);
}
int main() {
/* register a signal handler for SIGSEGV */
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = segv_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
/* raise SIGSEGV */
int* a = 0;
*a = 1;
return 0;
}
コンパイルして実行、segfault
と表示されたらgdb attach
で追えるはず
おわりに
セグフォじゃなくても他のシグナル(SIGFPE
とか)を捕捉することも可能。
あとcoredumpでも同じようなことができるような気はするが、プログラムが大きくなってくるとdumpのファイルサイズが大きすぎて大変なことになったりするのでこっちの方がシンプルで良い気がする。