重写signal handler在debug的时候非常有用,比如发生了segment fault,如果想知道内存访问出错的地址,就可以通过写自己的信号处理函数,打印出错地址。
首先看sigaction函数。通过man sigaction可以看到详细信息。
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
第一个参数是指要处理的信号,第二个参数是sigaction 结构体,包含具体的handler信息,以下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
第一个和第二个参数都是handle,但是只能同时指定一个。第二个可以得到更多的信息。其他参数可以查看man。我们再重点介绍以下siginfo_t这个结构。
siginfo_t {
int si_signo; /* Signal number */
int si_code; /* Signal code */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
sigval_t si_value; /* Signal value */
void *si_addr; /* Memory location which caused fault */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
...
}
这里列举了部分成员,对debug很重要的是si_addr 和si_code。前者说明了出错的地址,后者提供出错码,结合信号可以查到具体的原因。
example
#include <signal.h>
#include "stdio.h"
void
handler(int sig, siginfo_t *info, void *ucontext)
{
printf("si_signo: %d, si_code: %d, si_pid: %d, si_value: %d, si_addr: %p\n", info->si_signo, info->si_code, info->si_pid, info->si_value, info->si_addr);
signal(sig, SIG_DFL);
}
int main() {
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &act, NULL);
int *p=(int *) 10;
*p = 123;
return 0;
}
编译运行,得到:
si_signo: 11, si_code: 1, si_pid: 10, si_value: 0, si_addr: 0xa
si_addr就是出错地址,可以看到我们确实是因为非法访问了10这个地址。