Linux 信号捕获堆栈信息
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> // #include <signal.h> // sigaction #include <execinfo.h> // backtrace static void on_sig_segv(int signo, siginfo_t* info, void* ctx) { void* buf[256] = { 0 }; int size, i; char** strings; char line[1024]; char format[32] = { 0 }; pid_t pid = getpid(); StringBuilder<char> builder; // 0.参数校验 // 1.获取崩溃调用栈 size = backtrace(buf, 256); strings = backtrace_symbols(buf, size); // 2.获取时间信息 format_local_time(format, 32); // 3.格式化输出 // 3.1 输出信号信息 memset(line, 0, 1024); snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] @SIGNAL: %d\n", format, pid, signo); builder.Append(line); // 3.2 输出内存地址 memset(line, 0, 1024); snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] @Memory Location: %p\n", format, pid, info->si_addr); builder.Append(line); // 3.3 输出寄存器信息 //memset(line, 0, 1024); //snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] @Memory Location: %p\n", format, pid, context->uc_mcontext.gregs[REG_EFL]); //builder.Append(line); // 3.4 输出堆栈信息 for (i = 0; i < size; i++) { memset(line, 0, 1024); snprintf(line, 1023, "%s[ABORT] [ on_sig_segv %d] %s\n", format, pid, strings[i]); builder.Append(line); } free(strings); /* 设计说明: 每条信息单独打印,很多堆栈信息都被打印在一起,导致最终信息混乱,不容易观察,因此重新修改打印方式,改成一起输出 */ // 4.进程锁 lockf(STDERR_FILENO, F_LOCK, 0); fprintf(stderr, "%s\n", builder.ToString().c_str()); lockf(STDERR_FILENO, F_ULOCK, 0); /* 设计说明 使用信号捕捉函数可以捕捉信号,但是捕捉信号之后不会生成coredump文件,某些场景执行还是需要coredump文件帮助分析的 因此设计如下代码,捕捉信号之后再次发送信号 这里必须恢复默认信号处理,否则会造成信号处理死循环。 */ #ifdef TEST_DEBUG signal(signo, SIG_DFL); /* 恢复信号默认处理 */ raise(signo); /* 重新发送信号 */ #else _exit(1); // Don't use exit() to avoid be hanged in functions registered with `atexit' and `on_exit' #endif // TEST_DEBUG } static void signal_handler(int signo) { LOGI("[ signal_handler ] pid=%d recv signo=%d", getpid(), signo); switch (signo) { case SIGINT: case SIGTERM: LOGI("[ signal_handler ] Service Ready to Exit ."); exit(0); break; default: break; } } void signal_sigsegv() { /* 知识补充 SIGSEGV即 11 信号,一般是内存错误引起的,打印堆栈信息有助于我们分析错误,coredump文件也可以,但是太费磁盘了。 */ struct sigaction action; sigemptyset(&action.sa_mask); action.sa_sigaction = on_sig_segv; action.sa_flags = SA_SIGINFO; sigaction(SIGABRT, &action, NULL); sigaction(SIGBUS, &action, NULL); sigaction(SIGSEGV, &action, NULL); /* 知识补充 SIGPIPE信号是服务必须忽略的信号,对端关闭套接字的场景太多了,必须忽略该信号,不然服务大概率会崩溃。 */ signal(SIGPIPE, SIG_IGN); signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); } int main(int argc, char* argv[]) { return 0; }
备注:
在多进程场景下打印堆栈信息,最好先用缓冲区将所有的堆栈信息存储起来,然后一起输出。