转自:C/C++捕获段错误,打印出错的具体位置(精确到哪一行)

修订:2013-02-16

其实还可以使用 glibc 的 backtrace_symbols 函数,把栈帧各返回地址里面的数字地址翻译成符号描述的

修订:2011-06-11

背景知识:

· 在linux/unix中的信号处理机制,知道signal函数与sigaction的区别

· 段错误的概念,CPU中断处理的步骤,中断向量表的分类

· 知道CPU Exception分为Fault、trap和abort,了解他们的基本区别

· 段错误和浮点错误属于Fault,产生Fault时会将出错指令的地址入栈,而不是下一条将执行指令的地址

· 在linux/unix里可以通过调用backstrace来获取栈帧的信息

· 文中用到的几个头文件和函数,都属于glibc,所以不用担心出现找不到头文件和链接错误的情况

· addr2line是个系统自带的小工具,用来转换编译出来的地址和源码行号

背景知识大家可以看书,google,看手册(建议可以简单阅读一下本文列出来的参考资料)…,这里不想粘贴大量的背景知识,本文主要介绍在 linux / unix 里面,如何捕获段错误并输出发生错误时的代码执行路径,最后还提供了一个封装好的头文件。

OK,下面直奔主题:

——先要抓住段错误,别让它跑了

捕获段错误的方式很简单,针对段错误的信号调用 sigaction 注册一个处理函数就可以了。

struct sigaction act;

int sig = SIGSEGV;

sigemptyset(&act.sa_mask);

act.sa_sigaction = OnSIGSEGV;

act.sa_flags = SA_SIGINFO;

if(sigaction(sig, &act, NULL)<0)

{

perror("sigaction:");

}

信号处理函数

void OnSIGSEGV(int signum, siginfo_t *info, void *ptr)

{

//TO DO: 输出堆栈信息

abort();

}

——接下来,分析出错时的函数调用路径

发生段错误时的函数调用关系体现在栈帧上,可以通过在信号处理函数中调用 backstrace 来获取栈帧信息,backstrace 的具体描述可google之/阅读头文件execinfo.h。修改后的处理函数如下:

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 catched when running code at %x\n", (char*)array[i] - 1);

}

abort();

}

——进一步定位到出错的具体位置

要想输出出错的具体位置,必须用到信号处理函数的第三个参数,在linux/unix环境下,该指针指向一个ucontext_t结构。这个结构的具体情况,可以通过阅读头文件ucontext.h得知。此结构体里面包含了发生段错误时的寄存器现场,其中就包含EIP寄存器,该寄存器的内容正是段错误时的指令地址(因为段错误是一种Fault)。

进一步修正后的信号处理函数如下:

void OnSIGSEGV(int signum, siginfo_t *info, void *ptr)

{

void * array[25];

int nSize = backtrace(array, sizeof(array)/sizeof(array[0]));

for (int i=nSize-3; i>2; i--){ /* 头尾几个地址不必输出 */

/* 对array修正一下,使地址指向正在执行的代码 */

printf("signal[%d] catched when running code at %x\n", signum, (char*)array[i] - 1);

}

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] catched when running code at %x\n", signum, eip); /* 出错地址 */

}else{

printf("signal[%d] catched when running code at unknown address\n", signum);

}

abort();

}

——调用函数的路径、出错的位置都输出了,但是你能看懂输出么

好了,现在栈帧里面的地址和出错位置的地址都已经以十六进制的形式输出了,但是这是编译后的地址,而不是源码的行号,你能看懂么?所以还需要借助一个linux/unix自带的小工具addr2line,将这些打印出来的指令地址转换为行号、函数名。

执行情况的一个示例:

[root@suse tcpBreak]# ./a.out

signal[11] catched when running code at 804861d

signal[11] catched when running code at 8048578

signal[11] catched when running code at 804855a

[root@ suse tcpBreak]# addr2line 804861d 8048578 804855a -s -C -f -e a.out

main

newsig.cpp:55

oops()

newsig.cpp:32

error(int)

newsig.cpp:27

上面输出的内容,其具体含义是:

捕获的信号序号是 11 (SIGSEGV)

执行路径是第52行--第32行--第27行

调用关系是main--oops--error,在error函数内部,即文件的第27行发生了段错误。

——一点讨论

· 你可能已经阅读了 execinfo.h,发现其中有一个 backtrace_symbols,想通过调用这个函数来输出stack frame上面的函数名…你不妨试一下

· 将 backtrace 得到的 array 地址元素减 1 就能得到调用地点么?的确是这样的,减 1 不保证地址落到函数调用时跳转指令的起始处,但可以保证指向了该指令的最后一个字节,而该指令地址经addr2line转换后[f2] ,就对应了发生函数调用的行号。

· 可不可以不调用 backstrace 来得到栈帧中的内容?可以的,因为这些内容都在栈里,你要是明确地知道偏移,就可以得知函数调用栈,但是要费很多心思,而且估计你自己写的模仿 backstrace 的代码,可移植性成了问题。

· 通过 gdb 调试 core文件 不是直接看得到内存映像么,还有必要搞得这么复杂么?一般情况下当然不必要,上面所列解决方法的优点在无法正常产生 core 文件的情况[f3] 下才得以体现。

· 需要在编译时添加选项 -g 么?当然需要了,不在可执行文件中记录行号信息,addr2line上哪里去找行号。否则只能得到函数名称,无法得到行号信息。

——头疼,想直接用行不行,能来个直接可以用的代码么

这里提供一个头文件(见附件segvCatch.rar),但是不保证没有bug哦。使用方法很简单,只需要在main函数所在源文件包含该头文件即可。

该头文件捕获了浮点错误和段错误,像上面示例所说的,在出错时会向 STDOUT 输出一系列地址后退出程序,再使用 addr2line 对输出的地址进行转换,bingo,调用路径一目了然展示在你眼前啦!

标注:

[f1]调用函数时,会将函数返回地址入栈,此返回地址为返回后将执行的指令地址。

[f2]事实上,test()翻译成若干汇编指令,指向这些指令对应区域的所有地址将被addr2line转换为调用test()对应的行号。

C/C++捕获段错误,打印出错的具体位置(精确到哪一行)_转的更多相关文章

  1. C/C++捕获段错误,打印出错的具体位置(精确到哪一行)

    修订:2013-02-16 其实还可以使用 glibc 的 backtrace_symbols 函数,把栈帧各返回地址里面的数字地址翻译成符号描述的 修订:2011-06-11 背景知识: · 在li ...

  2. 利用Backtrace来捕获段错误堆栈信息

    具体参考文档:https://blog.csdn.net/gatieme/article/details/84189280 测试Demo: #include <execinfo.h> #i ...

  3. gdb调试段错误及使用

    在编程调试中,经常出现段错误,此时可用gdb调试.具体方法为注册段错误信号处理函数,在处理函数中启动gdb.具体代码如下: void segv_handler(int no) { ]; ]; FILE ...

  4. linux/unix 段错误捕获【续】

    本文为“在C/C++中捕获段错误,打印出错的具体位置”的续篇,进一步解决涉及动态链接库的情况.   背景知识: ·linux/unix下动态链接库的基本原理 ·/proc/pid/maps文件的基本格 ...

  5. linux/unix 段错误捕获_转

    转自:linux/unix 段错误捕获[续] 本文为“在C/C++中捕获段错误,打印出错的具体位置”的续篇,进一步解决涉及动态链接库的情况.   背景知识: ·linux/unix下动态链接库的基本原 ...

  6. 用gdb调试程序笔记: 以段错误(Segmental fault)为例

    用gdb调试程序笔记: 以段错误(Segmental fault)为例[转] 1.背景介绍2.程序中常见的bug分类3.程序调试器(如gdb)有什么用4.段错误(Segmental fault)介绍5 ...

  7. linux Segmentation faults 段错误详解

    什么是段错误 下面是来自 Answers.com 的定义: A segmentation fault (often shortened to segfault) is a particular err ...

  8. Linux环境下段错误的产生原因及调试方法小结(转)

    最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且 项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation F ...

  9. Linux环境下段错误的产生原因及调试方法小结

    转载自http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...

随机推荐

  1. 【LeetCode】142. Linked List Cycle II (2 solutions)

    Linked List Cycle II Given a linked list, return the node where the cycle begins. If there is no cyc ...

  2. Wunderlist 云端任务管理(Todo list)工具

    Wunderlist 是一个云端任务管理(Todo list)工具,支持 iPhone, iPad, Android, Windows, Mac OSX 以及 Web 端轻松同步,实现了真正意义上的跨 ...

  3. JFinal常见问题和知识点笔记

    1.当主键Id命名不是“id”时,应该显式地将自定义的id指出来 例如: Db.deleteById("post_user","user_id", 5); 2. ...

  4. 采用异步来实现重新连接服务器或者重新启动服务 C#中类的属性的获取 SignalR2简易数据看板演示 C#动态调用泛型类、泛型方法 asp .net core Get raw request. 从壹开始前后端分离[.NetCore 不定期更新] 38 ║自动初始化数据库

    采用异步来实现重新连接服务器或者重新启动服务 开启异步监听,不会导致主线程的堵塞,在服务异常断开后一直检测重新连接服务,成功连接服务后通知各个注册的客户端! #region 检测断线并重连OPC服务 ...

  5. django 数据库获取值

    数据库表中的内容如下: models.Step_Type.objects.values()获取model中的符合条件的值, 实际为把表中的所有值都查询出来,如查询结果为:<QuerySet [{ ...

  6. java读取url中json文件中的json数据

    有时候需要远程从其他接口中获取json数据,如果遇到返回的json数据是一个文件而不直接是数据,那么可以通过以下方法进行读取: /** * 从数据接口获取到数据 * @return * @throws ...

  7. MongoDB索引原理

    转自:http://www.mongoing.com/archives/2797 为什么需要索引? 当你抱怨MongoDB集合查询效率低的时候,可能你就需要考虑使用索引了,为了方便后续介绍,先科普下M ...

  8. 简易C#动态加载dll(实现插件化)

    可以通过该方法来实现程序插件化. 假设A,B两个类,A类为宿主,B类为插件需要加载到A类中: class Program { public interface IHellow { void Hello ...

  9. [sql]mysql启停脚本

    写了个较为完善的mysql多实例的启动停止脚本. [root@lanny 3307]# cat mysql #!/bin/sh [ $# != 1 ]&&{ echo "US ...

  10. 苹果开发小记(一):NSString 的比较用法

    转自:http://blog.sina.com.cn/s/blog_897dd7be0100teh6.html 做了几个月的苹果,很多的思想方法都可以遵循一定规律来做的.NSString 比较字符串, ...