由pthread库版本不一致导致的段错误

摘要:
没有办法,只得开gdbserver远程调试。在发生Segmentationfault的地方首先加载一下动态库的符号,然后看一下堆栈,是这样的:infostack#00x2b17b4e8inmemcpy()from/home/roy/mips-libs/lib/libc.so.0#10x2b19f3ccin__libc_pthread_init()from/home/roy/mips-libs/lib/libc.so.0#20x0042a1b8in__pthread_initialize_minimal()#30x2b19f50cin__uClibc_init()from/home/roy/mips-libs/lib/libc.so.0#40x2b083e40in_dl_get_ready_to_run()from/home/roy/mips-libs/lib/ld-uClibc.so.0#50x2b0842ecin??)在memcpy()函数里面发生段错误,那不用说了,肯定传入了空指针。查看寄存器和汇编代码,进一步确认一下:disasmemcpyDumpofassemblercodeforfunctionmemcpy:0x2b1af4d0:b0x2b1af4e80x2b1af4d4:movev1,a00x2b1af4d8:addiua2,a2,-10x2b1af4dc:addiua1,a1,10x2b1af4e0:sbv0,00x2b1af4e4:addiuv1,v1,10x2b1af4e8:bnezla2,0x2b1af4d80x2b1af4ec:lbuv0,00x2b1af4f0:jrra0x2b1af4f4:movev0,a0Endofassemblerdump.inforegisterszeroatv0v1a0a1a2a3R000000000181020a42b1d33a02b1f09f82b1f09f800000000000000b800000000t0t1t2t3t4t5t6t7R800000000000000040000000400000001000000002b0c4000000000180042a1b8s0s1s2s3s4s5s6s7R162b0b36f4000000052b0c40002b1742340000000000000004000000010000002ft8t9k0k1gpsps8raR24000002b62b1af4d000000000000000002b1f35107ffe77707ffe78802b1d33ccstatuslohibadvaddrcausepc010003130001257f000002cb00000000808004082b1af4e8fcsrfirrestart000000000000000000000000发生段错误的原因用红色字体标出来了,$a1寄存器的值为0,也就是空指针,memcpy()函数中还尝试从$a1的地址中读数据。继续按图索骥,向下查看堆栈,首先看__libc_pthread_init()函数:disas__libc_pthread_initDumpofassemblercodeforfunction__libc_pthread_init:0x2b1d33a0:luigp,0x20x2b1d33a4:addiugp,gp,3680x2b1d33a8:addugp,gp,t90x2b1d33ac:addiusp,sp,-320x2b1d33b0:swra,280x2b1d33b4:swgp,160x2b1d33b8:movea1,a00x2b1d33bc:lwt9,-327320x2b1d33c0:lwa0,-305360x2b1d33c4:jalrt90x2b1d33c8:lia2,1840x2b1d33cc:lwgp,160x2b1d33d0:lwra,280x2b1d33d4:addiusp,sp,320x2b1d33d8:jrra0x2b1d33dc:lwv0,-30532Endofassemblerdump.这里发现首先把$a0寄存器的值赋给了$a1,然后$a0寄存器另作他用。

前几天工作中遇到一个奇怪的问题,程序编译好之后一运行,就发生 segmentation fault. 另一个奇怪的问题是,删掉部分无用的代码(至少在程序启动时不会被调用),编译出来的程序稍微小了一点,就可以运行了。

发生 Segmentation fault 的程序,写在 main() 函数内的 log 都没有打印出来,因此断定是库的问题,但要跟踪确定问题到底发生在哪里,还是费了一番力气。先截个图:

由pthread库版本不一致导致的段错误第1张

由于程序是在开发板上运行的,不能直接调试,而且是MIPS汇编,此前没有接触过,不过幸好还算简单。没有办法,只得开 gdbserver 远程调试。在发生 Segmentation fault 的地方首先加载一下动态库(shared library)的符号,然后看一下堆栈,是这样的:

(gdb) info stack
#0  0x2b17b4e8 in memcpy () from /home/roy/mips-libs/lib/libc.so.0
#1  0x2b19f3cc in __libc_pthread_init () from /home/roy/mips-libs/lib/libc.so.0
#2  0x0042a1b8 in __pthread_initialize_minimal ()
#3  0x2b19f50c in __uClibc_init () from /home/roy/mips-libs/lib/libc.so.0
#4  0x2b083e40 in _dl_get_ready_to_run () from /home/roy/mips-libs/lib/ld-uClibc.so.0
#5  0x2b0842ec in ?? () from /home/roy/mips-libs/lib/ld-uClibc.so.0
warning: GDB can't find the start of the function at 0x2b0842eb.
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb)

在 memcpy() 函数里面发生段错误,那不用说了,肯定传入了空指针。查看寄存器和汇编代码,进一步确认一下:

(gdb) disas memcpy
Dump of assembler code for function memcpy:
0x2b1af4d0 <memcpy+0>:  b       0x2b1af4e8 <memcpy+24>
0x2b1af4d4 <memcpy+4>:  move    v1,a0
0x2b1af4d8 <memcpy+8>:  addiu   a2,a2,-1
0x2b1af4dc <memcpy+12>: addiu   a1,a1,1
0x2b1af4e0 <memcpy+16>: sb      v0,0(v1)
0x2b1af4e4 <memcpy+20>: addiu   v1,v1,1
0x2b1af4e8 <memcpy+24>: bnezl   a2,0x2b1af4d8 <memcpy+8>
0x2b1af4ec <memcpy+28>: lbu     v0,0(a1)
0x2b1af4f0 <memcpy+32>: jr      ra
0x2b1af4f4 <memcpy+36>: move    v0,a0
End of assembler dump.
(gdb) info registers
          zero       at       v0       v1       a0       a1       a2       a3
 R0   00000000 181020a4 2b1d33a0 2b1f09f8 2b1f09f8 00000000 000000b8 00000000 
            t0       t1       t2       t3       t4       t5       t6       t7
 R8   00000000 00000004 00000004 00000001 00000000 2b0c4000 00000018 0042a1b8 
            s0       s1       s2       s3       s4       s5       s6       s7
 R16  2b0b36f4 00000005 2b0c4000 2b174234 00000000 00000004 00000001 0000002f 
            t8       t9       k0       k1       gp       sp       s8       ra
 R24  000002b6 2b1af4d0 00000000 00000000 2b1f3510 7ffe7770 7ffe7880 2b1d33cc 
        status       lo       hi badvaddr    cause       pc
      01000313 0001257f 000002cb 00000000 80800408 2b1af4e8 
          fcsr      fir  restart
      00000000 00000000 00000000 
(gdb)

发生段错误的原因用红色字体标出来了,$a1寄存器的值为0,也就是空指针,memcpy() 函数中还尝试从 $a1 的地址中读数据。

那么为什么 $a1 寄存器的值为0呢,它又应该是个什么值?继续按图索骥,向下查看堆栈,首先看__libc_pthread_init() 函数:

(gdb) disas __libc_pthread_init
Dump of assembler code for function __libc_pthread_init:
0x2b1d33a0 <__libc_pthread_init+0>:     lui     gp,0x2
0x2b1d33a4 <__libc_pthread_init+4>:     addiu   gp,gp,368
0x2b1d33a8 <__libc_pthread_init+8>:     addu    gp,gp,t9
0x2b1d33ac <__libc_pthread_init+12>:    addiu   sp,sp,-32
0x2b1d33b0 <__libc_pthread_init+16>:    sw      ra,28(sp)
0x2b1d33b4 <__libc_pthread_init+20>:    sw      gp,16(sp)
0x2b1d33b8 <__libc_pthread_init+24>:    move    a1,a0
0x2b1d33bc <__libc_pthread_init+28>:    lw      t9,-32732(gp)
0x2b1d33c0 <__libc_pthread_init+32>:    lw      a0,-30536(gp)
0x2b1d33c4 <__libc_pthread_init+36>:    jalr    t9
0x2b1d33c8 <__libc_pthread_init+40>:    li      a2,184
0x2b1d33cc <__libc_pthread_init+44>:    lw      gp,16(sp)
0x2b1d33d0 <__libc_pthread_init+48>:    lw      ra,28(sp)
0x2b1d33d4 <__libc_pthread_init+52>:    addiu   sp,sp,32
0x2b1d33d8 <__libc_pthread_init+56>:    jr      ra
0x2b1d33dc <__libc_pthread_init+60>:    lw      v0,-30532(gp)
End of assembler dump.
(gdb)

这里发现首先把 $a0 寄存器的值赋给了 $a1,然后 $a0 寄存器另作他用。那么在执行到红色代码那一行的时候,$a0 寄存器应该也是 0,我们向下再找,就要观察 $a0 寄存器了。继续看 __pthread_initialize_minimal () 函数:

(gdb) disas __pthread_initialize_minimal
Dump of assembler code for function __pthread_initialize_minimal:
0x0042a194 <__pthread_initialize_minimal+0>:    lui     gp,0x5
0x0042a198 <__pthread_initialize_minimal+4>:    addiu   gp,gp,20252
0x0042a19c <__pthread_initialize_minimal+8>:    addu    gp,gp,t9
0x0042a1a0 <__pthread_initialize_minimal+12>:   addiu   sp,sp,-32
0x0042a1a4 <__pthread_initialize_minimal+16>:   sw      ra,28(sp)
0x0042a1a8 <__pthread_initialize_minimal+20>:   sw      gp,16(sp)
0x0042a1ac <__pthread_initialize_minimal+24>:   lw      t9,-30268(gp)
0x0042a1b0 <__pthread_initialize_minimal+28>:   jalr    t9
0x0042a1b4 <__pthread_initialize_minimal+32>:   move    a0,zero
0x0042a1b8 <__pthread_initialize_minimal+36>:   lw      gp,16(sp)
0x0042a1bc <__pthread_initialize_minimal+40>:   lw      v1,-32716(gp)
0x0042a1c0 <__pthread_initialize_minimal+44>:   sw      v0,2772(v1)
0x0042a1c4 <__pthread_initialize_minimal+48>:   lw      ra,28(sp)
0x0042a1c8 <__pthread_initialize_minimal+52>:   jr      ra
0x0042a1cc <__pthread_initialize_minimal+56>:   addiu   sp,sp,32
End of assembler dump.
(gdb)

哦,找到了,原来是在 __pthread_initialize_minimal() 函数中,$a0 寄存器被清空了,那么后面的 Segmentation fault 几乎顺理成章了。但是稍微一想,肯定就发现问题了,这个地方的代码没有发现什么分支,如果有另外一个程序可以正常运行的话,要么没有调用 __pthread_initialize_minimal() 函数,要么调用了另外一个不同版本的 __pthread_initialize_minimal() 函数。

带着这个疑问,我们跟踪一下正常运行的程序(暗自庆幸还有一个可供对比的样本)。但是调试这个正常运行的程序也费了一番力气,因为断点不好设置,因为 libc.so.0 并不是一起来就加载的,而是由一个过程,而且远程调试也不支持设置 load library 的断点。没有办法,只得我们自己寻找了,首先把断点设在 _dl_load_elf_shared_library() 函数上面,每断一次,就查看一下加载的是哪个库:

(gdb) continue
Continuing.

Breakpoint 1, 0x2b83e1e8 in _dl_load_elf_shared_library () from /home/roy/mips-libs/lib/ld-uClibc.so.0
(gdb) printf "%s\n", $s4+1
librt.so.0
(gdb)

第一次加载了一个 librt.so.0,继续这个过程,直到 libc.so.0 被加载,然后就可以在 __libc_pthread_init () 这个函数上面打断点了:

(gdb) break __libc_pthread_init         
Breakpoint 2 at 0x2b95b3bc
(gdb) del 1
(gdb) continue
Continuing.

Breakpoint 2, 0x2b95b3bc in __libc_pthread_init () from /home/roy/mips-libs/lib/libc.so.0
(gdb)

好,断下来了,看堆栈:

(gdb) info stack
#0  0x2b95b3bc in __libc_pthread_init () from /home/roy/mips-libs/lib/libc.so.0
#1  0x2b8a8eac in __pthread_initialize_minimal () from /home/roy/mips-libs/lib/libpthread.so.0
#2  0x2b95b50c in __uClibc_init () from /home/roy/mips-libs/lib/libc.so.0
#3  0x2b83fe40 in ?? ()
(gdb)

已经发现问题了,我们看到,的确出现了一个不同的 __pthread_initialize_minimal() 函数,这个函数在 libpthread.so.0 中,而先前出错的那个,不知道在哪里,应该是在我们程序本身。看一下这个函数:

(gdb) disas __pthread_initialize_minimal
Dump of assembler code for function __pthread_initialize_minimal:
0x2b8a8e84 <__pthread_initialize_minimal+0>:    lui     gp,0x2
0x2b8a8e88 <__pthread_initialize_minimal+4>:    addiu   gp,gp,-10676
0x2b8a8e8c <__pthread_initialize_minimal+8>:    addu    gp,gp,t9
0x2b8a8e90 <__pthread_initialize_minimal+12>:   addiu   sp,sp,-32
0x2b8a8e94 <__pthread_initialize_minimal+16>:   sw      ra,28(sp)
0x2b8a8e98 <__pthread_initialize_minimal+20>:   sw      gp,16(sp)
0x2b8a8e9c <__pthread_initialize_minimal+24>:   lw      t9,-32292(gp)
0x2b8a8ea0 <__pthread_initialize_minimal+28>:   lw      a0,-32340(gp)
0x2b8a8ea4 <__pthread_initialize_minimal+32>:   jalr    t9
0x2b8a8ea8 <__pthread_initialize_minimal+36>:   nop
0x2b8a8eac <__pthread_initialize_minimal+40>:   lw      gp,16(sp)
0x2b8a8eb0 <__pthread_initialize_minimal+44>:   lw      v1,-32744(gp)
0x2b8a8eb4 <__pthread_initialize_minimal+48>:   sw      v0,14948(v1)
0x2b8a8eb8 <__pthread_initialize_minimal+52>:   lw      ra,28(sp)
0x2b8a8ebc <__pthread_initialize_minimal+56>:   jr      ra
0x2b8a8ec0 <__pthread_initialize_minimal+60>:   addiu   sp,sp,32
End of assembler dump.
(gdb)

我们看到,$a0 寄存器的值是从 $gp-32340 这样寻址来的,看一下此时的 $a0 寄存器,发现它的内容和某个全局变量的地址是一样的:

(gdb) p/x $a0
$2 = 0x2b8be410
(gdb) info variable __pthread_functions
All variables matching regular expression "__pthread_functions":

Non-debugging symbols:
0x2b8be410  __pthread_functions
(gdb)

当然这是题外话。剩下的代码就一样了,$a0 赋给 $a1 ,从 $a1 读数据。

为什么一样的 Makefile 会生成两个不同的 __pthread_initialize_minimal() 函数?正当大惑不解的时候,灵光一闪,看一下运行时用到的 libpthread.so.0 和链接时用到的 libpthread.a 吧(用 objdump -S 看汇编代码):

在动态库 libpthread.so.0 中的 __pthread_initialize_minimal() 是这样的:

0000be84 <__pthread_initialize_minimal>:
    be84:	3c1c0002 	lui	gp,0x2
    be88:	279cd64c 	addiu	gp,gp,-10676
    be8c:	0399e021 	addu	gp,gp,t9
    be90:	27bdffe0 	addiu	sp,sp,-32
    be94:	afbf001c 	sw	ra,28(sp)
    be98:	afbc0010 	sw	gp,16(sp)
    be9c:	8f9981dc 	lw	t9,-32292(gp)
    bea0:	8f8481ac 	lw	a0,-32340(gp)
    bea4:	0320f809 	jalr	t9
    bea8:	00000000 	nop
    beac:	8fbc0010 	lw	gp,16(sp)
    beb0:	8f838018 	lw	v1,-32744(gp)
    beb4:	ac623a64 	sw	v0,14948(v1)
    beb8:	8fbf001c 	lw	ra,28(sp)
    bebc:	03e00008 	jr	ra
    bec0:	27bd0020 	addiu	sp,sp,32

在静态库 libpthread.a 中的 __pthread_initialize_minimal() 是这样的:

000010f4 <__pthread_initialize_minimal>:
    10f4:	3c1c0000 	lui	gp,0x0
    10f8:	279c0000 	addiu	gp,gp,0
    10fc:	0399e021 	addu	gp,gp,t9
    1100:	27bdffe0 	addiu	sp,sp,-32
    1104:	afbf001c 	sw	ra,28(sp)
    1108:	afbc0010 	sw	gp,16(sp)
    110c:	8f990000 	lw	t9,0(gp)
    1110:	0320f809 	jalr	t9
    1114:	00002021 	move	a0,zero
    1118:	8fbc0010 	lw	gp,16(sp)
    111c:	8f830000 	lw	v1,0(gp)
    1120:	ac620014 	sw	v0,20(v1)
    1124:	8fbf001c 	lw	ra,28(sp)
    1128:	03e00008 	jr	ra
    112c:	27bd0020 	addiu	sp,sp,32

一目了然了,正常运行的程序是从动态库中找符号,段错误的程序从静态库中找符号。那么,应该是我的代码中有使用 pthread 库的代码,恰恰是被删除的那一部分中,所以较大的程序,只是因为链接了 libpthread.a。毅然将 Makefile 中的 -lpthread 去掉,大功告成。

如今轻描淡写的几句话,当时不知费了多少力气。这个开发板是从某供应商买来的,他们提供的库不一样,我也没什么话可说了。

免责声明:文章转载自《由pthread库版本不一致导致的段错误》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇sabaki and leelazeroJQUERY PLUGIN:BARCODE条形码插件下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

centos6.5上安装guacamole

一、工作安排 在centos6.5上安装guacamole。安装guacamole前需要先安装jdk和tomcat。 二、具体步骤 一、安装jdk     1.下载jdk压缩文件 本次选择jdk1.8.0_65     2.新建/usr/java文件夹,将jdk压缩包解压到 /usr/java下,改名为jdk1.8 3.配置java环境变量 编辑 /etc...

ubuntu下命令行安装jdk --转载

1.ubuntu使用的是openjdk,所以我们需要先找到合适的jdk版本。在命令行中输入命令: $apt-cache search openjdk 返回结果列表(因个人电脑而有所不同): default-jdk-doc - Standard Java or Java compatible Development Kit (documentation) …...

【转】G++ 处理 /usr/bin/ld: cannot find -lc

原文网址:http://blog.sina.com.cn/s/blog_67bbb71101010tto.html 用g++编译C++程序时显示出:/usr/lib/ld: cannot find -lc/usr/lib/ld: cannot find -lgcc_s/usr/lib/ld: cannot find -lm/usr/lib/ld: cann...

opencv配置过程 (cmake,vs2013,qt 5.4)

平台及软件: Windows 7 X86 Visual Studio 2013 OpenCV3.0.0 Cmake3.3 1、下载Windows下的安装文件OpenCV-3.0.0.exe,解压,选择需要的安装目录即可。(本文为F:\opencv) 注意相应的目录不能包含中文。 2、Cmake编译 执行CMake,用于把OpenCV的源码生成对应的VS工程...

Centos 安装boost库

1.在http://www.boost.org/下载boost安装包boost_1_65_1.tar.gz 2.在Centos上解压tar -zxvf boost_1_65_1.tar.gz后,cd进入boost_1_65_1目录 3.安装boost库到指定目录 ./b2 install --prefix=/home/dj/lib/boost/  4.如果...

Win32编程day01 学习笔记

Win32 Windows编程  1 Windows编程基础 2 Windows文字的编码 3 窗口程序 4 窗口消息 5 菜单和加速键 6 绘图 7 对话框 8 基本控件  ..... 一 Windows编程基础  1 Win32应用程序的基本类型    1.1 控制台程序      不需要完善的Windows窗口,可以使用DOS窗口      的方式显...