在Linux C/C++中捕获段错误
关于段错误
所谓的段错误就是指访问的内存超出了系统所给这个程序的内存空间,在编程中几类做法容易导致段错误,基本上是错误地使用指针引起的。
1) 访问系统数据区,尤其是往系统保护的内存地址写数据最常见就是给一个指针以0地址。
2) 内存越界(数组越界,变量类型不一致等):访问到不属于你的内存区域。
解决方法:我们在用C/C++语言写程序的时候,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个内存访问越界的错误谈谈如何快速定位这些"段错误"的语句。
抓住段错误
在Linux中,程序在进行异常的内存操作后,会发出一个SIGSEGV 信号,我们只需要捕获到这个信号就可以对段错误进行后期处理。
如何处理、定位段错误
发生段错误时的函数调用关系体现在栈帧上,可以通过在信号处理函数中调用 backstrace 来获取栈帧信息,
void OnSIGSEGV(int signum, siginfo_t *info,void *ptr)
{
void * array[25]; /* 25 层,太够了 : ),你也可以自己设定个其他值 */
int nSize = backtrace(array, sizeof(array)/sizeof(array[0]));
for (int i=nSize-3; i>=2; i--){ /* 头尾几个地址不必输出,看官要是好奇,输出来看看就知道了 */
/* 修正array使其指向正在执行的代码 */[f1]
printf("SIGSEGV catchedwhen running code at %x\n", (char*)array[i] - 1);
}
abort();
}
接下来就是准确的获取段错误现场,在不同的处理器平台下,其处理方式不一样。在x86平台下Linux中有一个ucontext_t结构,个结构的具体情况,可以通过阅读头文件ucontext.h得知。此结构体里面包含了发生段错误时的寄存器现场,其中就包含EIP寄存器,该寄存器的内容正是段错误时的指令地址(因为段错误是一种Fault)。
if (NULL != ptr){
ucontext_t* ptrUC =(ucontext_t*)ptr;
int *pgregs =(int*)(&(ptrUC->uc_mcontext.gregs));
int eip = pgregs[REG_EIP];
if (eip != array[i]){ /* 有些处理器会将出错时的EIP 也入栈 */
printf("signal[%d]catched when running code at %x\n", signum, (char*)array[i] - 1);
}
printf("signal[%d] catchedwhen running code at %x\n", signum, eip); /* 出错地址 */
}else{
printf("signal[%d] catchedwhen running code at unknown address\n", signum);
}
通过以上两种方式输出的都是关于堆栈地址,对编译原理不熟悉的人是不会分析的,此时我们就需要借助工具来进行分析了,当让,如果你的输出程序不包含调试信息则不会准确的输出段错误位置,要保证我们的程序编译选项包含-g这个选项。当让,资深的程序员只需要得到段错误的堆栈信息就基本能分析出出错位置了。毕竟我们都是新手,需要借助工具。
Addr2line 这个工具是Linux平台下用来将地址信息转换为行号等信息的,当把地址转换到行号后就能找到出错位置啦。这个时候就可以认真分析代码了。
前面提到可以通过捕获SIGSEGV消息以捕获段错误,根据上一篇的方法是可以捕获到普通的段错误的,但是细心的童鞋们会发现,如果我们的段错误发生在库中,则通过addr2line这个工具得到的错误地址就不对了。实际上,通过堆栈信息我们可以知道某个库在动态加载到内存后的实际地址,通过这个内存中的开始地址,我们就可以计算实际静态文件偏移地址。
例如:
Libxxx.so 在Maps 中输入为
…
40018000-40019000 rw-p 00000000 00:09 17255/mnt/hgfs/share/net/tcpBreak/libxxx.so
…
可以看到,此库在动态加载时的开始地址为 0x40018000
假如通过上诉方法定位到地址 0x4001824b
那么实际上在静态文件中的偏移为 0x4001800 – 0x4001824b = 0x24b
于是我们通过addr2line 24b -s -C -f -e libxxx.so
就会定位到实际的出错地址了。