首页 > 代码库 > Linux 信号

Linux 信号

每个进程都需要有个信号处理函数,以捕捉异常信号。

我们在写代码时,有时会有内存非法使用,这种问题一般比较难定位。但是如果有信号处理函数,就可以在捕捉到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

技术分享

 

也可以反编译可执行文件,来查看地址。

 

Linux 信号