linux
虽然在execinfo.h中使用backtrace()函数打印堆栈跟踪并在得到分段错误时优雅地退出已被建议,我没有看到任何必要的复杂之处,以确保所产生的回溯指向故障的实际位置(至少对于某些体系结构-x86&ARM)。
当您进入信号处理程序时,堆栈帧链中的前两个条目在信号处理程序中包含一个返回地址,在libc中包含一个在Sigaction()中的返回地址。在信号(故障位置)之前调用的最后一个函数的堆栈帧丢失。
电码#ifndef _GNU_SOURCE#define _GNU_SOURCE#endif#ifndef __USE_GNU#define __USE_GNU#endif#include #include
#include #include #include #include #include /*
This structure mirrors the one found in /usr/include/asm/ucontext.h */typedef struct _sig_ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
struct sigcontext uc_mcontext;
sigset_t uc_sigmask;} sig_ucontext_t;void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext){
void * array[50];
void * caller_address;
char ** messages;
int size, i;
sig_ucontext_t * uc;
uc = (sig_ucontext_t *)ucontext;
/* Get the address at the time the signal was raised */#if defined(__i386__) // gcc specific
caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific#elif defined(__x86_64__) // gcc specific
caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific#else#error Unsupported architecture.
// TODO: Add support for other arch.#endif
fprintf(stderr, "signal %d (%s), address is %p from %p\n",
sig_num, strsignal(sig_num), info->si_addr,
(void *)caller_address);
size = backtrace(array, 50);
/* overwrite sigaction with caller's address */
array[1] = caller_address;
messages = backtrace_symbols(array, size);
/* skip first stack frame (points here) */
for (i = 1; i
{
fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
}
free(messages);
exit(EXIT_FAILURE);}int crash(){
char * p = NULL;
*p = 0;
return 0;}int foo4(){
crash();
return 0;}int foo3(){
foo4();
return 0;}int foo2(){
foo3();
return 0;}int foo1(){
foo2();
return 0;}int main(int argc, char ** argv){
struct sigaction sigact;
sigact.sa_sigaction = crit_err_hdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
{
fprintf(stderr, "error setting signal handler for %d (%s)\n",
SIGSEGV, strsignal(SIGSEGV));
exit(EXIT_FAILURE);
}
foo1();
exit(EXIT_SUCCESS);}
输出量signal 11 (Segmentation fault), address is (nil) from 0x8c50[bt]: (1) ./test(crash+0x24) [0x8c50][bt]: (2) ./test(foo4+0x10) [0x8c70][bt]:
(3) ./test(foo3+0x10) [0x8c8c][bt]: (4) ./test(foo2+0x10) [0x8ca8][bt]: (5) ./test(foo1+0x10) [0x8cc4][bt]:
(6) ./test(main+0x74) [0x8d44][bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]
在信号处理程序中调用Backtrace()函数的所有危险仍然存在,不应忽视,但我发现这里描述的功能对于调试崩溃非常有帮助。
需要注意的是,我提供的示例是在Linuxforx86上开发/测试的。我也成功地在ARM上实现了这个uc_mcontext.arm_pc而不是uc_mcontext.eip.