Linux信号应用之黑匣子程序设计

发表于:2016-6-07 13:29

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:时间轨迹    来源:51Testing软件测试网采编

  1. 何为黑匣子程序及其必要性
  飞机上面的黑匣子用于飞机失事后对事故的时候调查,同理,程序的黑匣子用于程序崩溃后对崩溃原因进程定位。其实Linux提供的core dump机制就是一种黑匣子(core文件就是黑匣子文件)。但是core文件并非在所有场景都适用,因为core文件是程序崩溃时的内存映像,如果程序使用的内存空间比较大,那产生的core文件也将会非常大,在64bit的操作系统中,该现象更为显著。但是,其实我们定位程序崩溃的原因一般只需要程序挂掉之前的堆栈信息、内存信息等就足够了。所以有的时候没有必要使用系统自带的core文件机制。
  阅读本文前,推荐先看一下我的另外一篇博客《Linux 的 core 文件》,里面讲解了core文件,并介绍了一些Linux信号的基本知识。
  2. 黑匣子程序设计
  程序异常时,往往会产生某种信号,内核会对该信号进行处理。所以设计黑匣子程序的实质就是我们定义自己的信号处理函数,来代替内核的默认处理。在我们的信号处理函数中,我们可以将我们想要的信息保存下来(比如程序崩溃时的堆栈信息),以方便后面问题的定位。
  下面我们先给出一个我写的程序,然后边分析程序边讲具体如何设计一个黑匣子程序:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <execinfo.h>
/* 定义一个数据结构用来保存信号 */
typedef struct sigInfo
{
int     signum;
char    signame[20];
} sigInfo;
/* 增加我们想要捕捉的异常信号,这里列举了6个 */
sigInfo sigCatch[] = {
{1, "SIGHUP"}, {2, "SIGINT"}, {3, "SIGQUIT"},
{6, "SIGABRT"}, {8, "SIGFPE"}, {11, "SIGSEGV"}
};
/* 我们自定义的信号处理函数 */
void blackbox_handler(int sig)
{
printf("Enter blackbox_handler: ");
printf("SIG name is %s, SIG num is %d\n", strsignal(sig), sig);
// 打印堆栈信息
printf("Stack information:\n");
int j, nptrs;
#define SIZE 100
void *buffer[100];
char **strings;
nptrs = backtrace(buffer, SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL)
{
perror("backtrace_symbol");
exit(EXIT_FAILURE);
}
for(j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);
free(strings);
_exit(EXIT_SUCCESS);
}
/* 有bug的程序,调用该程序,将随机产生一些异常信号 */
void bug_func()
{
int rand;
struct timeval tpstart;
pid_t  my_pid = getpid();
// 产生随机数
gettimeofday(&tpstart, NULL);
srand(tpstart.tv_usec);
while ((rand = random()) > (sizeof(sigCatch)/sizeof(sigInfo)));
printf("rand=%d\n", rand);
//随机产生异常信号
switch(rand % (sizeof(sigCatch)/sizeof(sigInfo)))
{
case 0:
{
// SIGHUP
kill(my_pid, SIGHUP);
break;
}
case 1:
{
// SIGINT
kill(my_pid, SIGINT);
break;
}
case 2:
{
// SIGQUIT
kill(my_pid, SIGQUIT);
break;
}
case 3:
{
// SIGABRT
abort();
break;
}
case 4:
{
// SIGFPE
int a = 6 / 0;
break;
}
case 5:
{
// SIGSEGV
kill(my_pid, SIGSEGV);
break;
}
default:
return;
}
}
int main()
{
int i, j;
struct  sigaction   sa;
// 初始化信号处理函数数据结构
memset(&sa, 0, sizeof(sa));
sa.sa_handler = blackbox_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
for (i = 0; i < sizeof(sigCatch)/sizeof(sigInfo); i++)
{
// 注册信号处理函数
if(sigaction(sigCatch[i].signum, &sa, NULL) < 0)
{
return EXIT_FAILURE;
}
}
bug_func();
while(1);
return EXIT_SUCCESS;
}
  2.1 定义一些数据结构
  这里我们定义了一个sigInfo的数据结构,用来保存信号。利用这个数据结构我们可以将信号值与信号名映射起来。你可以在你的系统中使用 kill –l 命令去查看他们的对应关系。当然,在程序中,如果得到了信号值,也可以使用Linux提供的API函数strsignal来获取信号的名字,其函数原型如下:
  #include <string.h>
  char *strsignal(int sig);
  之后定义了一个全局变量sigCatch来增加我们想要处理的信号。
31/3123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号