Linux C信号编程:接管段错误信号SIGSEGV打印调用栈

有时候,线上环境没有开启coredump,这时把进程复位时的调用栈打印出来就非常有利于问题定位了。怎么做呢?也很简单,我们在程序中接管SGISEGV信号,在信号处理函数打印调用栈信息即可。

信号处理可以看:UNIX环境高级编程
内核机制可以看:Linux内核源代码情景分析
这两本书虽老了点,但写的确实很不错,相关系统API使用可以配合可能Linux man手册。

接管段错误信号后,在信号处理函数中打印当前调用栈即可。此时的栈信息可以回溯到之前发生异常时的函数栈帧。整个过程大致是这样的:用户太程序在运行,堆栈寄存器指向用户栈,访问非法内存,中断陷入内核,内核掉注册的信号处理函数,又切回到了用户态复用之前的栈。理解压栈原理,很容易从当前栈基址往前回溯整个站的,原理可以看:Linux C函数调用栈帧结构。并且,Linux也有API可以直接调用来打印调用栈。当然,如果栈内存被破坏了,就还原不出来了。

一个例子如下,记得编译的时候加上 -rdynamic 参数,可以打出符号名称。代码非常简单:

#include 
#include 
#include 
#include 
#include void ShowStack()
{int i;void *buffer[1024];int n = backtrace(buffer, 1024);char **symbols = backtrace_symbols(buffer, n);for (i = 0; i < n; i++) {printf("%s\n", symbols[i]);}
}void SigSegvProc(int signo) {if (signo == SIGSEGV) {printf("Receive SIGSEGV signal\n");printf("-----call stack-----\n");ShowStack();exit(-1);} else {printf("this is sig %d", signo);}
}void RegSig() {signal(SIGSEGV, SigSegvProc);
}void fun3() {printf("this is fun3\n");*(char*)0 = 1; // read nullptr
}void fun2() {printf("this is fun2\n");fun3();
}void fun1() {printf("this is fun1\n");fun2();
}int main() {RegSig();fun1();return 0;
}

其运行结果如下:

➜  sig gcc -g -rdynamic main.c
➜  sig ./a.out
this is fun1
this is fun2
this is fun3
Receive SIGSEGV signal
-----call stack-----
./a.out(ShowStack+0x2e) [0x55d6574ebb38]
./a.out(SigSegvProc+0x33) [0x55d6574ebbec]
/lib/x86_64-linux-gnu/libc.so.6(+0x3ef20) [0x14a39f418f20]
./a.out(fun3+0x15) [0x55d6574ebc3c]
./a.out(fun2+0x1a) [0x55d6574ebc5c]
./a.out(fun1+0x1a) [0x55d6574ebc79]
./a.out(main+0x18) [0x55d6574ebc94]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x14a39f3fbb97]
./a.out(_start+0x2a) [0x55d6574eba2a]

如果没有开coredump,那么不妨就自己接管段错误SIGSEGV信号,打印非法访问内存时的调用栈。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部