使用信号机制与系统_Unwind_Backtrace输出程序退出堆栈信息
1 描述
进程执行某些异常指令(比如除0或非法内存访问),或者其他进程或进程自己通过kill 调用向进程发送信号时,操作系统强制进程对信号作出处理。进程启动时操作系统设置默认的信号处理函数,通过signal、sigaction函数进程可以更改信号处理策略。Linux 系统提供有64种信号,使用kill -l可查看:
yanyg@t:~$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
不同信号默认行为不同,Man 7 Sginal对此有完整描述,如下是可能的处理策略:
行为 | 描述 |
---|---|
Term | 进程退出 |
Ign | 忽略 |
Core | 生成Core文件,需要同时设置ulimit -c |
Stop | 进程停止运行 |
Cont | 进程继续运行 |
并非所有信号均可Ign或Cont,部分信号在处理后,操作系统会强制进程退出,比如SIGKILL 或SIGSEGV信号。SIGKILL信号即不能被捕获,也不能被忽略。进程常见的信号包括终端挂起(SIGHUP)、键盘中断(SIGINT)、非法地址访问(SIGSEGV)、总线错误(SIGBUS)、杀死进程(SIGKILL)。
默认的信号处理函数在退出时,通常不能给出足够的信息帮助快速定位问题。同时,主动防御的编程技术中,在关键点会插入assert确保软件符合预期。无论信号或者assert,在退出时如能打印当前进程的调用栈,对于快速分析问题均有较大帮助。
本文描述进程在退出前(无论因信号或assert),打印调用栈的方法。预期实现如下几个目标:
- 准确记录调用栈,帮助快速定位分析问题;
- 能提供-g编译的二进制文件时,记录调用栈对应文件名、代码行;
- 支持多线程,退出时记录每个线程的调用栈;
- 如果进程已设置信号处理函数,在记录调用栈后调用;
- 结束后还原系统默认信号处理函数,并再次触发信号。
2 signal
3 assert
assert是主动防御机制,用于检测不可能发生的情况,记录信息包括assert所在的文件名、函数名、代码行与调用栈。考虑可扩展,允许提供回调函数,辅助打印更多信息。
#define _GNU_SOURCE #include <dlfcn.h> #include <stdint.h> #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <ucontext.h> #include <unistd.h> #include <unwind.h> #define sig_err(str) write(STDERR_FILENO, (str), strlen(str)) #define sig_out(str) write(STDERR_FILENO, (str), strlen(str)) static int pr_reg = 1; char *func_name(void *addr, char *buf) { Dl_info di; const void *name = "_end"; if (0 == dladdr(addr, &di)) { // error return "nil"; } if (di.dli_sname) { name = di.dli_sname; } sprintf(buf, "%s+%lx, base-offset %lx (%p)", name, addr - di.dli_saddr, addr - di.dli_fbase, di.dli_saddr); return buf; } _Unwind_Reason_Code read_stacktrace(struct _Unwind_Context *uc, void *p) { int n, used = 0; char buf[1024], fname[256]; uint64_t regs[16], r; void **rt, *caller = (void*)_Unwind_GetIP(uc); if (!caller) return _URC_END_OF_STACK; rt = (void**)uc; for (r = 0; r < 16; ++r) { if (rt[r] != NULL) regs[r] = _Unwind_GetGR(uc, r); else regs[r] = 0xCCCCCCCCEEEEEEEEUL; } if (pr_reg) { pr_reg = 0; used = snprintf(buf + used, 1024 - used, "Regs:\n\t"); for (r = 0; r < 16; ++ r) { n = snprintf(buf + used, 1024 - used, " %#018lx", regs[r]); used += n; if ((r+1)%4 == 0) { snprintf(buf + used, 1024 - used, "\n\t"); used += 2; } } n = snprintf(buf + used, 1024-used, "\n"); used += n; } n = snprintf(buf + used, 1024 - used, "Caller: %p(%s)\n", caller, func_name(caller, fname)); used += n; //snprintf(buf + used, 1024 - used, "RIP=%lx, RBP=%lx, RSP=%lx\n", // ((ucontext_t*)uc)->uc_mcontext.gregs[REG_RIP], // ((ucontext_t*)uc)->uc_mcontext.gregs[REG_RBP], // ((ucontext_t*)uc)->uc_mcontext.gregs[REG_RSP]); sig_out(buf); return _URC_NO_REASON; } void stacktrace() { /* in multithread environment, may be you add tid here */ _Unwind_Backtrace(read_stacktrace, 0); } static inline void __assert_stack(const char *expr, const char *file, const char *func, size_t line, void (*cb)(void*), void *arg) { ucontext_t uc; fprintf(stderr, "Assert expr: %s [%s, %s, %zu]\n", expr, file, func, line); getcontext(&uc); stacktrace(); if (cb) cb(arg); exit(1); } #define __assert_string_expr(expr) #expr #define assert_stack(expr, callback, arg) ({ \ if (expr) \ __assert_stack(#expr, __FILE__, __func__, \ __LINE__, callback, arg); \ (void)NULL; }) int main(int argc, char *argv[argc]) { int i = 50, j = 100; assert_stack(i*2 == j, NULL, NULL); return 0; }