突如死んでるプロセスがあってシグナルを受けてるのところまではわかったがどこから受けてるのかがわからず困っていたらkillsnoopというツールがbccにあったので使ってみた。これはkill(2)によって送信されるシグナルを追跡する機能を提供しているツールでシグナルの種類と結果までをみることができます。
こんな感じでオプションなしで実行するとシステム全体のシグナルについて監視を行える。送信PIDや受信PIDでターゲットを絞って監視を行えたりシグナル番号での監視も行えるようです。便利。
$ ./tools/killsnoop.py TIME PID COMM SIG TPID RESULT 14:23:45 48846 bash 2 48846 0 14:23:46 48846 bash 2 48846 0 14:23:46 48846 bash 2 48846 0
実装を眺める
- syscall__kill関数: kill()システムコールが呼び出されるたびにトリガーされます。この関数は、送信されるシグナルの情報を一時的なハッシュマップに保存します。
- do_ret_sys_kill関数: システムコールが終了したときに呼び出され、結果を記録し、パフォーマンスイベントを介してユーザースペースにデータを送信します。
int syscall__kill(struct pt_regs *ctx, int tpid, int sig) { u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; TPID_FILTER PID_FILTER SIGNAL_FILTER struct val_t val = {.pid = pid}; if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) { val.tpid = tpid; val.sig = sig; infotmp.update(&tid, &val); } return 0; }; int do_ret_sys_kill(struct pt_regs *ctx) { struct data_t data = {}; struct val_t *valp; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; valp = infotmp.lookup(&tid); if (valp == 0) { // missed entry return 0; } bpf_probe_read_kernel(&data.comm, sizeof(data.comm), valp->comm); data.pid = pid; data.tpid = valp->tpid; data.ret = PT_REGS_RC(ctx); data.sig = valp->sig; events.perf_submit(ctx, &data, sizeof(data)); infotmp.delete(&tid); return 0; }