在C/C++的项目线上环境部署的时候,会使用minidump的机制,来避免进程宕掉的时候生成巨大的core文件。比如,注册各种信号处理函数,记录发生宕机的backtrace到文件,然后把进程尽快的拉起。类似如下这样:
void minidump_handle(int sig)
{
// do-something
// 记录 backtrace
// 记录现场
exit(-1);
}
int install_minidump_handle()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = minidump_handle;
int ret = 0;
ret = sigaction(SIGBUS, &act, NULL);
if (ret != 0)
{
printf("failed to install minidump handle for SIGBUS. error: %d", errno);
return ret;
}
ret = sigaction(SIGSEGV, &act, NULL);
if (ret != 0)
{
printf("failed to install minidump handle for SIGSEGV. error: %d", errno);
return ret;
}
ret = sigaction(SIGABRT, &act, NULL);
if (ret != 0)
{
printf("failed to install minidump handle for SIGABRT. error: %d", errno);
return ret;
}
ret = sigaction(SIGFPE, &act, NULL);
if (ret != 0)
{
printf("failed to install minidump handle for SIGFPE. error: %d", errno);
return ret;
}
return 0;
}
一般会注册四个信号处理函数,这几个信号一般涵盖了新手写C++会掉的常见的坑。
SIGBUS
产生这个信号的原因,一般是访问直接对齐的地址导致的;
SIGSEGV
段错误,访问空指针,栈溢出等问题会产生这个信号;
SIGABRT
assert的时候,会产生这个信号 ;
SIGFPE
fpe : float 计算异常,比如除以0等会产生这个信号;
一切都看着很正常,似乎看起来很完美。但是,对于如下的情况,上面的注册的信号处理函数就不管用了。在函数栈上面开辟了10mb的空间,函数栈的大小一般是8MB,这样会导致栈溢出。会产生sigsegv信号,但是上面的信号处理函数是无法正常工作的。程序会直接coredump。
void test_stackoverflow()
{
// 在函数栈上面开辟一个巨大的空间 10mb
char data[10*1024*1024];
printf("data size %s", sizeof(data));
}
测试程序如下,sigsegv信号没有被捕捉到,程序直接core掉。
#include
#include
#include
#include
#include
void minidump_handle(int sig)
{
printf("catch stack overflow sig : %d\n", sig);
exit(-1);
}
int install_minidump_handle()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = minidump_handle;
int ret = 0;
ret = sigaction(SIGSEGV, &act, NULL);
if (ret != 0)
{
return ret;
}
}
void test_illegal_pointer()
{
int *a = (int *)0x1;
int b = *a;
}
int main()
{
install_minidump_handle(); // 在栈上开辟巨大的空间的段错误不能被捕捉到
// test_illegal_pointer(); // 访问空指针的段错误 可以被捕捉到
test_stackoverflow();
}
那么,问题来了,为什么会出现这样的情况? 查下来发现,如果是在栈上开辟巨大的空间导致的段错误的时候,信号注册的处理函数已经无法在栈上进行调用信号处理的函数了。
问题定位了,接下来就是如何去解决?实验了一下,只要不要信号处理的函数放在栈上,声明为static 或者是new 出来的空间都是可以的。
可以写成如下的方式, 这样就可以正常的捕捉由于声明大的栈变量导致的段错误了。
int install_minidump_handle_in_static()
{
static char stack[SIGSTKSZ];
stack_t ss = {
.ss_size = SIGSTKSZ,
.ss_sp = stack,
};
struct sigaction sa = {
.sa_handler = minidump_handle,
.sa_flags = SA_ONSTACK
};
sigaltstack(&ss, 0);
sigfillset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, 0);
}
完成的测试程序如下:
#include
#include
#include
#include
#include
#include "minidump.h"
//#define _XOPEN_SOURCE 700
void minidump_handle(int sig)
{
printf("catch stack overflow sig : %d\n", sig);
exit(-1);
}
int install_minidump_handle()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = minidump_handle;
int ret = 0;
ret = sigaction(SIGSEGV, &act, NULL);
if (ret != 0)
{
return ret;
}
}
int install_minidump_handle_in_static()
{
static char stack[SIGSTKSZ];
stack_t ss = {
.ss_size = SIGSTKSZ,
.ss_sp = stack,
};
struct sigaction sa = {
.sa_handler = minidump_handle,
.sa_flags = SA_ONSTACK
};
sigaltstack(&ss, 0);
sigfillset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, 0);
}
void test_stackoverflow()
{
test_stackoverflow();
//char data[10*1024*1024];
//printf("data size %s", sizeof(data));
}
void test_illegal_pointer()
{
int *a = (int *)0x1;
int b = *a;
}
int test()
{
test_illegal_pointer();
}
int main()
{
// if(1)
// {
// install_minidump_handle();
// test_stackoverflow();
// //test_illegal_pointer();
// }
// else
// {
// install_minidump_handle_in_static();
// test_stackoverflow();
// //test_illegal_pointer();
// }
install_minidump_handle();
//test_illegal_pointer();
test();
return 0;
}
那找到了问题的所在,就比较容易解决了。
- 修改linux 信号捕捉的方式
- 对于在栈上开辟这样比较巨大结构体变量,比较好的做法是声明为static
声明为static 的时,把栈上的变量放到的static区,可能导致程序的二进制变大。这可能是附带来的一个小问题。
补充:
- 如何获得backtrace
void debug_dump_backtrace()
{
printf("[backtrace]\n");
void *array[16];
size_t size = backtrace(array, 16);
char **strings = backtrace_symbols(array, static_cast(size));
if (strings == NULL)
{
perror("backtrace_symbols ret null");
return ;
}
for (size_t i=0; i
这样既可以记录到宕机现场的函数调用栈,有避免了生成巨大的core-dump文件的负担。