l-c-skill

Segmentation fault段错误调试总结

Segmetation fault也叫做段错误,引发的原因有好多,这里我们只说一下段错误发生时的调试方法。

方法1:加打印printf。

这是最基本的往往也很有效的方法,在哪里Core掉就会在哪里停止打印–一目了然。同时这种方法也存在一个致命缺陷:如果恰巧Core掉的地方没加打印而程序代码又非常庞大又可能是多线程的,那查找问题等同于大海捞针。

方法2:gdb调试。

加gdb调试往往能在Core dump时抓到,甚至能抓到哪一个文件哪个类哪个函数哪一行,甚是精确。要确保GDB能抓到可用信息要做一些准备:

  • 加-g 参数,这样才会有调试信息。 我想是个程序员就应该知道吧。
  • 在Makefile 中加上 -fstack-protector 和-fstack-protector-all 信息,确保函数调用栈不丢失,当然只能是一定程度的不丢失,要完成保留住是不太可能的,但起码可以得到栈顶函数。

有了上面两点对大多数的Segmentation fault都能抓住,但是函数调用栈彻底乱掉或者在动态库so中Core而这个库编译时没有加-g参数,这些情况就gdb就无能为力了。

方法3:手动获取函数调用栈。

这种方法其实是借住两个系统函数backtrace和backtrace_symbol来获取函数调用栈的,把这两个函数放在信号处理函数中:当收到 SIGSEG时在信号处理函数中调用这两个函数打印函数调用栈,在没用GDB调试的时候这种方法可以代替gdb的一部分功能,这听起来是不是非常酷啊,来看一看实现吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

static void SignalHandle(int sig)
{
void *array[20];
size_t size;
char **strings;
int i;
size = backtrace(array, 10);
strings = backtrace_symbols(array, size);
printf("SIGNAL ocurre %d, stack tarce:\n", sig);
printf("obtained %d stack frames.\n", size);

for (i = 0; i < size; i++)
printf("%s\n", strings);

free(strings);
printf("stack trace over!\n");
exit(0);
}

int main(int argc, char **argv)
{
signal(SIGSEGV, SignalHandle);
//...程序主体
}

当然这种方法在没有GDB时候会大显身手,经过实验就是有gdb的时候这种方法有时比gdb抓到调用栈要多一层;当然这种方法和用gdb调试一样要加-g和栈保护参数-fstack-protector-fstack-protector-all。其缺点就是抓到的调用栈无效,这是什么意思呢?有时发生core dump,能定位到甚至哪一行,但是那一行根本没有明显的错误;或者追到没有调试信息的动态库里如glibc。当然这些情况大多数调试方法都无能为力,只能依靠程序员的经验了。

方法4:经验之谈。

如果我们的程序是多线程的,发生core dump用以上方法均无效,除了仔细排查代码外,还有这么一方法让我们缩小范围。

c语言全局变量那些事 “C++的数组不支持多态”?

代码执行的效率

深入理解C语言

对象的消息模型

读书笔记:对线程模型的批评

C语言的谜题

C语言函数实现的另类方法

谁说C语言很简单?

C语言下的错误处理的问题

C语言结构体里的成员数组和指针

C技巧:结构体参数转成不定参数

坚持原创技术分享,您的支持将鼓励我继续创作!