博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux下段错误调试技巧
阅读量:2399 次
发布时间:2019-05-10

本文共 2862 字,大约阅读时间需要 9 分钟。

更新于2019.04.17

我们写的程序, 尤其是C/C++程序有时候会段错误, 而且往往发生在部署环境而非调试环境, 对问题定位带来很大困难. 这时一般有两种方法来解决问题, 一种是生成core dump文件, 然后用gdb调试这个文件; 另一种是不生成core dump文件, 而使用其他工具来定位问题.

生成core dump文件并用gdb调试

最简单的方法是运行ulimit -c unlimited命令, 然后在错误发生后用gdb调试这个文件. 但这种方法往往不好用, 比如段错误发生时没有生成core dump文件, 或core dump文件过大不完整等等.

使用dmesg和addr2line命令

这种方法是使用dmesg和addr2line命令. 这需要代码以-g选项编译 比如以下代码故意产生段错误:

#include 
int main(void){ int *p = NULL; *p = 0; printf("bad\n"); return 0;}

编译运行这个程序, 立即会发生段错误:

$ gcc -O3 -g -o test test.c$ ./test段错误(吐核)

这时先调用dmesg命令查看段错误信息:

$ dmesg | grep segfault[422855.897248] test[63448]: segfault at 0 ip 0000000000400449 sp 00007ffd06202b70   error 6 in test[400000+1000]

注意其中指令指针寄存器(IP)的值, 接下来调用addr2line命令, 把IP所指的地址转换为源码行号:

$addr2line -e test 0000000000400449/root/tmp/test.c:6

可见排查出源码第6号有错. 如果想看dmesg输出的错误发生在什么时候, 可以通过以下命令把行首的时间戳转化为我们习惯的时间显示(把其中的"时间"换成行首中括号里的值即可, 比如422855.897248):

$ date -d "1970-01-01 UTC `echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d' ')+时间" |  bc `seconds"

如果段错误出在动态库而非可执行程序中, 在调用addr2line命令时, 需要将dmesg输出中IP的值减去行最后的地址值, 比如下面一行中

[422855.897248] test[63448]: segfault at 0 ip 0000000000400449 sp 00007ffd06202b70 error 6 intest[400000+1000]

addr2line的参数 = 400449 -400000.

下面是动态库段错误的示例, 假设我们有3个文件, 主程序test.c, 动态库头文件foo.h和实现文件foo.c, 内容分别如下.

test.c:

#include "foo.h"int main(void){    foo();    return 0;}

foo.h:

#ifndef __FOO_LIB_H__#define __FOO_LIB_H__int foo(void);#endif

foo.c:

#include "foo.h"int foo(){    int *p = 0;    *p = 0;    return 0;}

先编译动态库, 再编译主程序, 让它链接动态库, 最后运行之:

$ gcc -O3 -g -o libfoo.so -shared -fPIC foo.c$ gcc -O3 -g -o test test.c -L. -lfoo$ export LD_LIBRARY_PATH=.$ ./test段错误(吐核)

调用dmesg:

$dmesg | grep segfault[423801.507232] test[63800]: segfault at 0 ip 00007f8adeb08680 sp 00007ffeb7f29ab8  error 6 in libfoo.so[7f8adeb08000+1000]

IP值减去动态库地址值(00007f8adeb08680 -7f8adeb08000=680), 调用addr2line, 注意-e参数后文件名改为动态库名:

$ addr2line -e libfoo.so 680/root/tmp/foo.c:6

让程序段错误时自动输出堆栈信息并调试

以上方法还是显得不够方便, 还要调用dmesg命令等. 我们可以使用execinfo.h里的backtrace函数及信号处理机制, 来让程序在发生段错误时自动打印调用堆栈:

#include 
#include
#include
#include
#include
#include
#include
#include
#define BACKTRACE_SIZE 256void segv_handler(int sig){ void *func[BACKTRACE_SIZE]; char **symb = NULL; int size; size = backtrace(func, BACKTRACE_SIZE); backtrace_symbols_fd(func, size, STDERR_FILENO); exit(1);}int main(void){ int *p = NULL; signal(SIGSEGV, segv_handler); *p = 0xdeadbeef; return 0;}

然后以-g选项编译, 运行:

$ gcc -O3 -g -o auto auto.c$ ./auto./auto[0x400654]/lib64/libc.so.6(+0x35250)[0x7fbbe4283250]./auto[0x400543]/lib64/libc.so.6(__libc_start_main+0xf5)[0x7fbbe426fb35]./auto[0x40057e]

输出的调用堆栈逆序打印, 最先调用的在最下面, 我们可以判断出是第3行的 ./auto[0x400543] 导致了段错误, 因为前面2行是信号SIGSEGV的处理函数调用. 运行addr2line:

$ addr2line -e auto 0x400543/root/tmp/auto.c:29

可知第29行代码 *p = 0xdeadbeef; 引起错误.

参考

转载地址:http://ohdob.baihongyu.com/

你可能感兴趣的文章
安装J2SE 1.3.1 for Linux的方法(转)
查看>>
Apche日志系列(5):高级技术(转)
查看>>
谈谈数据从sql server数据库导入mysql数据库的体验(转)
查看>>
解读startx(转)
查看>>
RMAN配置DATAGUARD完整案例(主库基于ASM存储)
查看>>
Oracle分区表
查看>>
关联查询子查询效率简单比照
查看>>
Enterprise Manager之oracle性能与管理
查看>>
linux启动盘制作
查看>>
struts2 标签的使用之一 s:if,siterator使用
查看>>
11gR2批量操作EM性能监控报表集
查看>>
[转帖]广交会使用频率最高的英语
查看>>
职业经理人影响力自检手册(二)
查看>>
企业如何提好自己的内部需求?
查看>>
[分享]ERP实施工程师笔试题目
查看>>
看板管理概述(zt)
查看>>
IT审计中应注意的几个问题(zt)
查看>>
最美的七十个英语单词
查看>>
中国企业需要精益求精 (zt)
查看>>
第四章 计划工作概述
查看>>