从libc-2.27.so[7ff3735fd000+1e7000]崩溃回溯程序段错误segfault

简介

最近的应用程序遇到了点问题,它总是在退出的时候段错误崩溃。

段错误大家应该不陌生了,内存使用不当导致段错误。

通过尝试gdb core文件,dmesg,objdump库文件等方法终于定位到了崩溃原因。

这里作一记录。

gdb无效时使用dmesg调试

我打开了coredump配置:

在运行进程的终端执行:ulimit -c unlimited,该方法只能临时打开当前终端的内核转储,详细方法请参考:内核转储-coredump简介

程序崩溃时产生了core文件,但是gdb打印的情况如下:

Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007eff6effd9fd in ?? ()
[Current thread is 1 (LWP 19887)]
(gdb) bt
#0  0x00007eff6effd9fd in ?? ()
#1  0x000055e3e8345330 in ?? ()
#2  0x635027382a97e600 in ?? ()
#3  0x000055e3e8343f60 in ?? ()
#4  0x000055e3e7a99f67 in ?? ()
#5  0x000055e3e8345100 in ?? ()
#6  0x635027382a97e600 in ?? ()
#7  0x000055e3e8345100 in ?? ()
#8  0x00007ffcd31007e0 in ?? ()
#9  0x0000000000000000 in ?? ()
(gdb) 

这就尴尬了。我编译程序时加了-g呀,怎么是这样呢?

经查,出现这种情况可能是在编译时加了-O2选项,或者是程序链接的库没有-g编译。

没办法,由于当时机器上没有源码,只能进一步查查原因。

coredump行不通了,还有个dmesg。一般异常崩溃的程序,内核都会在日志里记录一下崩溃信息,直接在终端运行dmesg查看即可。

关于dmesg的更多介绍,请参考Linux内核日志查看之dmesg命令简介

刚崩溃时查看的话,最后一行应该就是。我的显示如下:

[1745123.819693] godserver[29078]: segfault at 1ff8 ip 00007ff3736949fd sp 00007fff9f148ba0 error 4 in libc-2.27.so[7ff3735fd000+1e7000]

其中,

  • 第一列为时间戳,使用dmesg -T可显示人类时间

  • 二列为进程名和进程id

  • 后面的3个奇葩数字分别为出错时的地址(用处不大)、发生错误时指令的地址、堆栈指针

  • error number为4,意思是用户态程序内存读出错,error 由三个bit位组成,它的具体含义如下:

    • bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
    • bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
    • bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址
  • 后面是崩溃的具体位置,发生在libc-2.27.so库内,libc在此程序中映射的内存基址为7ff3735fd000

  • 注意一点,新手可能会怀疑是不是库函数有问题啊?这个其实不用担心,遇到这种情况,99.9999999999%的可能性是自己的程序有问题。如果你真的发现库的问题,可以提交issue去了。

到这里,可以先分析一下,哪些操作会导致内存读崩溃:

  • 操作null指针
  • 把未初始化或非法指针交给函数处理
  • 读内存越界
  • 读取非法内存

这样范围很大,不可能一点一点看代码找吧。。。

其实,根据现有的信息,我们还是可以再做些努力的。

那就从libc入手吧。注意两个地址:

  • 崩溃时指令地址ip:00007ff3736949fd
  • 崩溃库的基地址:7ff3735fd000

两者相减,其实就得到了崩溃时库的位置:9 79FD。

下面我们来看一下相对代码段地址为979FD的地方是什么函数:

# -t为打印文件的符号表入口,具体可查看man page
# -T为打印文件的动态符号表入口
% objdump -tT /lib/x86_64-linux-gnu/libc.so.6 | grep 979
00000000000979c0 g    DF .text	0000000000000e31 (GLIBC_2.2.5) cfree
00000000000979c0 g    DF .text	0000000000000e31  GLIBC_2.2.5 __libc_free
00000000000979c0 g    DF .text	0000000000000e31  GLIBC_2.2.5 free

打印出来的函数是free。

结合之前的分析,读内存非法,调用free确实可能导致这个崩溃。很可能是free了非法的地址,即free了一个无效的指针。

到这里,为了验证一下自己的猜想,可以写个free无效指针的测试程序,运行一下,看看它是不是也会导致dmesg记录这样一条日志。

c++中可以导致底层free的函数也不多,如free,delete, 智能指针的析构等。可以重点查一下它们。

经过添加日志等方式定位,我的程序是在退出时,一个类析构时free了无效的指针。

使用gdb如虎添翼

得空的时候,我导入了一份源码,加上-g,去掉-O2编译扔上去,崩溃,就可以gdb看看了。

还没等到我gdb,程序崩溃时就输出了一堆东西:

*** Error in `./godserver': free(): invalid pointer: 0x00007ffeb5a483d0 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81299)[0x7fe119d7e299]
./godserver[0x43599a]
./godserver[0x43591d]
./godserver[0x4a2d79]
./godserver[0x4a2df9]
./godserver[0x445d4b]
./godserver[0x43ffe3]
/lib64/libc.so.6(+0x39ce9)[0x7fe119d36ce9]
/lib64/libc.so.6(+0x39d37)[0x7fe119d36d37]
/lib64/libc.so.6(__libc_start_main+0xfc)[0x7fe119d1f55c]
./godserver[0x4190d7]
======= Memory map: ========
00400000-005ea000 r-xp 00000000 fd:02 57716                              /home/ysj/work/godserver
007e9000-007ef000 r--p 001e9000 fd:02 57716                              /home/ysj/work/godserver
007ef000-007f0000 rw-p 001ef000 fd:02 57716                              /home/ysj/work/godserver
007f0000-007f1000 rw-p 00000000 00:00 0 
00f0d000-1491ad000 rw-p 00000000 00:00 0                                 [heap]
7fddd0000000-7fddd737b000 rw-p 00000000 00:00 0 
7fddd737b000-7fddd8000000 ---p 00000000 00:00 0 
7fddd8000000-7fdde0000000 rw-p 00000000 00:00 0 
7fdde0000000-7fdde8000000 rw-p 00000000 00:00 0 
7fdde8000000-7fddec000000 rw-p 00000000 00:00 0 
# 后面省略

可以看出,是free了无效的指针,哈哈。

接着上gdb:

gdb exe_file core_file

执行bt,就可以看出是具体是哪行代码搞的了,分分钟解决bug。

小节

对于开发中的程序,编译时加-g,这样出错之后好定位解决问题。

注意-g和-O2不要同时使用,既然要调试,就先不要优化啦。

在没有调试条件时,使用内核提供的信息也可以看出个大概,这样心里有数,不至于慌。

参考

Linux内核日志查看之dmesg命令简介
内核转储-coredump简介
一次segfault错误的排查过程

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值