每个进程都需要有个信号处理函数,以捕捉异常信号。
我们在写代码时,有时会有内存非法使用,这种问题一般比较难定位。但是如果有信号处理函数,就可以在捕捉到SEGV信号后打印出详细信息以定位问题。
下面写一个简单的例子,来定位非法内存访问。
#include <stdio.h>
#define __USE_GNU
#include <ucontext.h>
#include <sys/prctl.h>
#include <execinfo.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
static void show_maps()
{
char cmd[128];
snprintf(cmd, sizeof(cmd), "cat /proc/%d/maps", getpid());
system(cmd);
}
static void show_backtrace()
{
void *trace[32];
int trace_size;
char **messages;
trace_size = backtrace(trace ,32);
int idx;
messages = backtrace_symbols(trace, trace_size);
if (NULL != messages)
{
for (idx = 0; idx < trace_size; idx++)
{
printf("%s\n", messages[idx]);
}
free(messages);
}
}
static void show_regs(mcontext_t *regs)
{
char comm[20] = {‘\0‘};
prctl(PR_GET_NAME, comm);
printf("PID:%d,comm:%20s\n", getpid(), comm);
printf("EIP:%p\n",(void*) regs->gregs[REG_EIP]);
}
static void sighandler(int signo, siginfo_t *info, void *context)
{
ucontext_t *uc = (ucontext_t *)context;
switch (signo)
{
case SIGSEGV :
printf("Invalid mem!!! Fault addr:%p\n",(void*)( info->si_addr));
show_regs(&(uc->uc_mcontext));
show_backtrace();
show_maps();
exit(0);
break;
default:
break;
}
}
int register_signalHandler()
{
struct sigaction sa;
int ret;
sa.sa_sigaction = &sighandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
if (0 != sigaction(SIGSEGV, &sa, NULL))
{
printf("sigaction fail, errno:%d\n", errno);
return ret;
}
return 0;
}
void func()
{
printf("func!\n");
int *p;
*p = 8;
}
int main(void)
{
register_signalHandler();
func();
return 0;
}
在func函数中有个非法内存使用。
gcc -g signal.c -o signal
运行signal进程:
EIP寄存器地址在地址区间08048000~08049000是在进程的.text段。
我们可以通过addr2line进行定位:
76行正好是*p=8那一行。
如果SEGEV不是发生在进程的代码段,发生在某个so内。假如发生在0x7553654,而且so load到进程中的0xb7553000,那么addr2line应该查看地址0x654;
也可以在gdb中查看list *0x8048951
也可以反编译可执行文件,来查看地址。