看到标题大家可能会想,openssl和fPIC会有什么关系呢?这要从最近遇到的一个问题说起。由于openssl去年和今年被爆出了很多漏洞,而我们项目中用到了openssl较老的版本,所以需要重新编译openssl的新版本。这本应该是一间很简单的事,可是却遇到了问题。在RedHat 7.x平台上,编译之后的程序在跑的时候直接crash了,而且奇怪的是在RedHat AS4平台上没有问题。 废话不多说,上错误栈:1
2
3
4
Program terminated with signal 11 , Segmentation fault.
#0 0 xf7779875c in sha1_block_data_order () from /opt/xxx/xxx/libxxx.so
#1 0 xf7797926 in SHA1_Update () from /opt/xxx/xxx/libxxx.so
#2 0 x00000000 in ?? ()
以sha1_block_data_order
为关键字经过一番搜索,发现网上也有遇到类似crash问题的人。给出的解释是,因为多线程调用,openssl在多线程调用时为了保证线程安全,需要在每个调用线程里明确设置两个callback函数。引用如下:
Is OpenSSL thread-safe? Yes (with limitations: an SSL connection may not concurrently be used by multiple threads). On Windows and many Unix systems, OpenSSL automatically uses the multi-threaded versions of the standard libraries. If your platform is not one of these, consult the INSTALL file.
Multi-threaded applications must provide two callback functions to OpenSSL by calling CRYPTO_set_locking_callback() and CRYPTO_set_id_callback(), for all versions of OpenSSL up to and including 0.9.8[abc…]. As of version 1.0.0, CRYPTO_set_id_callback() and associated APIs are deprecated by CRYPTO_THREADID_set_callback() and friends. This is described in the threads(3) manpage.
本以为找着了问题的原因,然而在仔细搜索了代码之后发现我们已经设置了这两个callback。然后自己在redhat 7.3上面写了一个demo程序去调用,单线程的竟然也会crash!不过还好,本地可以复现。重新编译了一个debug版本的程序,用gdb一步一步的去跟。最后获取了比较详细的调用栈:1
2
3
4
5
6
7
8
9
10
11
sha1_block_data_order ()
#0 SHA1_Update (c=0x80d7f60 , data_=0xbfff5fe0 , len=8 ) at ../md32_common.h:307
#1 0x40350e27 in update (ctx=0xbfff5fa0 , data=0xbfff5fe0 , count=8 ) at m_sha1.c:80
#2 0x4034879a in EVP_DigestUpdate (ctx=0xbfff5fa0 , data=0xbfff5fe0 , count=8 )
at digest.c:244
#3 0x403e1bb3 in ssleay_rand_add (buf=0xbfff61c0 , num=32 , add =32 ) at md_rand.c:288
#4 0x40345cdc in RAND_add (buf=0xbfff61c0 , num=32 , entropy=32 ) at rand_lib.c:152
#5 0x403e298b in RAND_poll () at rand_unix.c:405
#6 0x403e24c3 in ssleay_rand_status () at md_rand.c:578
#7 0x40345de4 in RAND_status () at rand_lib.c:175
#8 0x40294a39 in _seedPRNG () at xxx.cpp:65
程序出错在程序给openssl发送随机种子时调用的sha1_block_data_order
函数里,以下是此函数的定义: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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
$A="eax" ;
$B="ebx" ;
$C="ecx" ;
$D="edx" ;
$E="edi" ;
$T="esi" ;
$tmp1="ebp" ;
&function_begin("sha1_block_data_order" );
&mov($tmp1,&wparam(0 ));
&mov($T,&wparam(1 ));
&mov($A,&wparam(2 ));
&stack_push(16 );
&shl($A,6 );
&add($A,$T);
&mov(&wparam(2 ),$A);
&mov($E,&DWP(16 ,$tmp1));
&set_label("loop" ,16 );
for ($i=0 ; $i<16 ; $i+=4 )
{
&mov($A,&DWP(4 *($i+0 ),$T));
&mov($B,&DWP(4 *($i+1 ),$T));
&mov($C,&DWP(4 *($i+2 ),$T));
&mov($D,&DWP(4 *($i+3 ),$T));
&bswap($A);
&bswap($B);
&bswap($C);
&bswap($D);
&mov(&swtmp($i+0 ),$A);
&mov(&swtmp($i+1 ),$B);
&mov(&swtmp($i+2 ),$C);
&mov(&swtmp($i+3 ),$D);
}
&mov(&wparam(1 ),$T);
&mov($A,&DWP(0 ,$tmp1));
&mov($B,&DWP(4 ,$tmp1));
&mov($C,&DWP(8 ,$tmp1));
&mov($D,&DWP(12 ,$tmp1));
for ($i=0 ;$i<16 ;$i++) { &BODY_00_15($i,@V); unshift (@V,pop (@V)); }
for (;$i<20 ;$i++) { &BODY_16_19($i,@V); unshift (@V,pop (@V)); }
for (;$i<40 ;$i++) { &BODY_20_39($i,@V); unshift (@V,pop (@V)); }
for (;$i<60 ;$i++) { &BODY_40_59($i,@V); unshift (@V,pop (@V)); }
for (;$i<80 ;$i++) { &BODY_20_39($i,@V); unshift (@V,pop (@V)); }
(($V[5 ] eq $D) and ($V[0 ] eq $E)) or die ;
&mov($tmp1,&wparam(0 ));
&mov($D,&wparam(1 ));
&add($E,&DWP(0 ,$tmp1));
&add($T,&DWP(4 ,$tmp1));
&add($A,&DWP(8 ,$tmp1));
&add($B,&DWP(12 ,$tmp1));
&add($C,&DWP(16 ,$tmp1));
&mov(&DWP(0 ,$tmp1),$E);
&add($D,64 );
&mov(&DWP(4 ,$tmp1),$T);
&cmp($D,&wparam(2 ));
&mov(&DWP(8 ,$tmp1),$A);
&mov($E,$C);
&mov(&DWP(12 ,$tmp1),$B);
&mov($T,$D);
&mov(&DWP(16 ,$tmp1),$C);
&jb(&label("loop" ));
&stack_pop(16 );
&function_end("sha1_block_data_order" );
可以发现其中用到了大量的寄存器,eax/ebx/ecx/edx等。后来突然联想到在编译openssl的时候我们加上了-fPIC选项,fPIC作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。那么会不会是-fPIC选项对于内嵌汇编代码的支持不好造成的呢。于是重新编译了一下openssl的库,问题果然解决了。 在问题解决之后又做了一些research,找到了一篇文章“gcc指定-fPIC编译的时候内嵌汇编需要注意的问题 ”。其中有提到:
gcc在生成位置无关代码的时候,内部使用了ebx作为基址寄存器。如果不使用内嵌汇编,那么gcc自然会帮助你维持ebx的值始终有效。但是如果使用了内嵌汇编,gcc常常就有点力不从心了,所以这时候,一定要自己留意保存好ebx的值。
所以造成sha1_block_data_order
crash的原因应该就是其中用到了ebx,而且没有自己去保存好它。而对于位置无关的代码内部使用了ebx作为基址寄存器,所以就出现了Segmentation fault。可是还有一个问题,为什么redhat AS4平台上也用了fPIC而没有问题呢。我想这应该是因为老版本的编译器对于这种情况的handle不是很好。因为redhat 7.x的gcc版本确实比较低了些。 希望我跨过的这个坑会对后来人有所帮助。