您的位置:首页 > 其它

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

2017-09-01 09:02 417 查看
本博客采自http://www.360doc.com/content/15/0206/20/7377734_446769046.shtml,说是来自于博客园,具体url不详。
软件排错就像侦探探案,其中的曲折、乐趣、以及最终搞定问题之后的成就感,都很另笔者兴奋。虽然本文不是笔者亲身经历,但独来仍津津有味。

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

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



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

1
2
3
4
5
6
7
8
9
10
(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() 函数里面发生段错误,那不用说了,肯定传入了空指针。查看寄存器和汇编代码,进一步确认一下:

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
(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() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(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 () 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(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() 函数上面,每断一次,就查看一下加载的是哪个库:

1
2
3
4
5
6
7
(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 () 这个函数上面打断点了:

1
2
3
4
5
6
7
8
(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)

 好,断下来了,看堆栈:

1
2
3
4
5
6
(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 中,而先前出错的那个,不知道在哪里,应该是在我们程序本身。看一下这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(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 寄存器,发现它的内容和某个全局变量的地址是一样的:

1
2
3
4
5
6
7
8
(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() 是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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() 是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 去掉,大功告成。

如今轻描淡写的几句话,当时不知费了多少力气。这个开发板是从某供应商买来的,他们提供的库不一样,我也没什么话可说了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: