如何才能让进程先捕捉SIGSEGV信号,打印出有用的方便定位问题的信息,然后再优雅地退出呢?
程序实操
SIGSEGV针对段错误信号进行处理!代码如下:段错误位置是crash()
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.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;
caller_address = (void *)uc->uc_mcontext.eip;
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);
array[1] = caller_address;
messages = backtrace_symbols(array, size);
/* 跳过第一个栈帧 */
for (i = 1; i < size && messages != NULL; ++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);
}
编译报错:test.c:29:46: error: ‘struct sigcontext’ has no member named ‘rip’
=> 这是因为机器平台架构的硬件寄存器对应不是这个名词,所以需要以下步骤修改:
1. uname -m
查看架构或者是清楚目标机器的架构
2. 修改sig_ucontext_t 对应使用的成员变量,如下:
架构 | 对应寄存器 |
---|---|
x86_64架构 | (64位Intel/AMD) rip:在64位模式下,rip寄存器用于存储下一条要执行的指令的地址。 |
i686架构(32位x86) | eip:在32位模式下,eip寄存器存储下一条要执行的指令的地址。这是rip在32位x86架构中的等价寄存器。 |
ARM架构 pc(Program Counter): | 在ARM架构中,pc寄存器存储下一条要执行的指令的地址。这相当于x86架构中的rip或eip。 |
其他架构 | 对于其他处理器架构(如PowerPC、MIPS、RISC-V等),也有各自的指令指针寄存器,通常称为PC(Program Counter)或类似的名称。 |
所以我将使用的rip改成了eip,编译可以通过了
3. 定位段错误位置
方法一:objdump
objdump -d a.out | grep 80486c3
定位了crash函数
方法二:rdynamic链接选项
gcc test.c -rdynamic
-rdynamic编译选项
通知链接器在生成可执行文件时,将所有全局符号(包括未导出的)加入到动态符号表中。这使得在运行时,通过dladdr()、backtrace()、backtrace_symbols()等相关函数可以获取到更详细的符号信息,包括函数名称。
定位sig_ucontext_t
其实sig_ucontext_t的定义之处,也可以查到当前所定义的成员变量名称
/usr/include目录下寻找sigcontext.h和ucontext.h
关于 struct sigcontext 支持的架构,它广泛应用于支持Linux操作系统的多种架构,包括但不限于:
x86 (i386, i686)
x86_64 (amd64)
ARM (arm, armv7, armv8)
AArch64 (arm64)
PowerPC (ppc, ppc64, ppc64le)
MIPS (mips, mipsel, mips64, mips64el)
RISC-V (rv32, rv64)
总结
不过!!!你会发现程序一般不会对SIGSEGV信号进行处理,那是因为SIGSEGV是不可靠信号(1-31)
不可靠信号无法保证一定会被处理!!!所以在程序崩溃段错误这种情况下,用了也可能是白用。。。。。。