16.29 导出符号的版本控制
http://scz.617.cn/unix/201507241413.txt
A: Masanori Goto 2015-07-24 14:13
$ gcc --version gcc (Debian 4.9.2-10) 4.9.2
$ vi test_0.c
#include <stdio.h>
int max ( int a, int b ) { int ret = ( a > b ? a : b );
printf( "max_0( %d, %d ) = %d\n", a, b, ret );
return( ret );
$ vi test_1.c
#include <stdio.h>
extern int max ( int a, int b );
int main ( int argc, char * argv[] ) { int ret = max( 1, 2 );
printf( "max() = %d\n", ret );
return( ret );
$ gcc -Wl,-soname,libtest.so -fPIC -shared -Wall -pipe -O0 -s -o libtest.so.0 test_0.c $ ln -s libtest.so.0 libtest.so $ gcc -Wall -pipe -O0 -s -o test_1 test_1.c -L. -ltest $ LD_LIBRARY_PATH=. ./test_1 max_0( 1, 2 ) = 2 max() = 2
$ vi test_2.c
#include <stdio.h>
int max_0 ( int a, int b ) { int ret = ( a > b ? a : b );
printf( "max_0( %d, %d ) = %d\n", a, b, ret );
return( ret );
} /* end of max_0 */
int max_2 ( int a, int b, int c ) { int d = ( a > b ? a : b ); int ret = ( d > c ? d : c );
printf( "max_2( %d, %d, %d ) = %d\n", a, b, c, ret );
return( ret );
} /* end of max_0 */
$ vi test_2.map
LIBTEST_1.0 { global :
max;
local :
*;
};
LIBTEST_2.0 { global :
max;
$ vi test_3.c
#include <stdio.h>
extern int max ( int a, int b, int c );
int main ( int argc, char * argv[] ) { int ret = max( 1, 2, 3 );
printf( "max() = %d\n", ret );
return( ret );
$ gcc -Wl,--version-script,test_2.map -Wl,-soname,libtest.so -fPIC -shared -Wall -pipe -O0 -s -o libtest.so.2 test_2.c $ ln -sf libtest.so.2 libtest.so $ gcc -Wall -pipe -O0 -s -o test_3 test_3.c -L. -ltest $ LD_LIBRARY_PATH=. ldd ./test_3 | grep libtest.so libtest.so => ./libtest.so (0xb773e000) $ LD_LIBRARY_PATH=. ldd ./test_1 | grep libtest.so libtest.so => ./libtest.so (0xb775a000) $ ls -l libtest.so lrwxrwxrwx 1 root root 12 Jul 24 15:52 libtest.so -> libtest.so.2*
$ LD_LIBRARY_PATH=. ./test_1 max_0( 1, 2 ) = 2 max() = 2 $ LD_LIBRARY_PATH=. ./test_3 max_2( 1, 2, 3 ) = 3 max() = 3
$ readelf -Ws test_1 | grep max 3: 00000000 0 FUNC GLOBAL DEFAULT UND max $ nm -D test_1 | grep max U max
$ readelf -Ws test_3 | grep max 1: 00000000 0 FUNC GLOBAL DEFAULT UND max@LIBTEST_2.0 (2) $ nm -D test_3 | grep max U max
$ readelf -Ws libtest.so.0 | grep max 8: 00000525 65 FUNC GLOBAL DEFAULT 11 max $ nm -D libtest.so.0 | grep max 00000525 T max
$ readelf -Ws libtest.so.2 | grep max 7: 00000586 86 FUNC GLOBAL DEFAULT 12 max@@LIBTEST_2.0 8: 00000545 65 FUNC GLOBAL DEFAULT 12 max@LIBTEST_1.0 $ nm -D libtest.so.2 | grep max 00000586 T max 00000545 T max $ readelf -WV libtest.so.2 ... Version definition section '.gnu.version_d' contains 3 entries: Addr: 0x00000000000002c8 Offset: 0x0002c8 Link: 4 (.dynstr) 000000: Rev: 1 Flags: BASE Index: 1 Cnt: 1 Name: libtest.so 0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: LIBTEST_1.0 0x0038: Rev: 1 Flags: none Index: 3 Cnt: 2 Name: LIBTEST_2.0 0x0054: Parent 1: LIBTEST_1.0 ...
test_1找max,但libtest.so.2中没有max,只有带版本的max,这种情况下用 .gnu.version_d中Index为2的那个版本。
test_3找"max@LIBTEST_2.0 (2)",libtest.so.2中有max@@LIBTEST_2.0。这个(2)不 知啥意思?
$ vi test_4.c
#include <stdio.h>
int max_0 ( int a, int b ) { int ret = ( a > b ? a : b );
printf( "max_0( %d, %d ) = %d\n", a, b, ret );
return( ret );
} /* end of max_0 */
int max_1 ( int a, int b ) { int ret = ( a > b ? a : b );
printf( "max_1( %d, %d ) = %d\n", a, b, ret );
return( ret );
} /* end of max_1 */
int max_1_5 ( int a, int b ) { int ret = ( a > b ? a : b );
printf( "max_1_5( %d, %d ) = %d\n", a, b, ret );
return( ret );
} /* end of max_1_5 */
int max_2 ( int a, int b, int c ) { int d = ( a > b ? a : b ); int ret = ( d > c ? d : c );
printf( "max_2( %d, %d, %d ) = %d\n", a, b, c, ret );
return( ret );
} /* end of max_0 */
asm
("
.symver max_0,max@ ;
.symver max_1,max@LIBTEST_1.0 ;
.symver max_1_5,max@LIBTEST_1.5 ;
.symver max_2,max@@LIBTEST_2.0 ;
");
$ vi test_4.map
LIBTEST_1.0 { global :
max;
local :
*;
};
LIBTEST_1.5 { global :
max;
} LIBTEST_1.0;
LIBTEST_2.0 { global :
max;
$ gcc -Wl,--version-script,test_4.map -Wl,-soname,libtest.so -fPIC -shared -Wall -pipe -O0 -s -o libtest.so.4 test_4.c $ ln -sf libtest.so.4 libtest.so $ LD_LIBRARY_PATH=. ./test_1 max_0( 1, 2 ) = 2 max() = 2 $ LD_LIBRARY_PATH=. ./test_3 max_2( 1, 2, 3 ) = 3 max() = 3
$ readelf -Ws libtest.so.4 | grep max 7: 00000688 86 FUNC GLOBAL DEFAULT 12 max@@LIBTEST_2.0 8: 00000647 65 FUNC GLOBAL DEFAULT 12 max@LIBTEST_1.5 9: 000005c5 65 FUNC GLOBAL DEFAULT 12 max 10: 00000606 65 FUNC GLOBAL DEFAULT 12 max@LIBTEST_1.0 $ nm -D libtest.so.4 | grep max 00000688 T max 00000647 T max 000005c5 T max 00000606 T max $ readelf -WV libtest.so.4 ... Version definition section '.gnu.version_d' contains 4 entries: Addr: 0x0000000000000318 Offset: 0x000318 Link: 4 (.dynstr) 000000: Rev: 1 Flags: BASE Index: 1 Cnt: 1 Name: libtest.so 0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: LIBTEST_1.0 0x0038: Rev: 1 Flags: none Index: 3 Cnt: 2 Name: LIBTEST_1.5 0x0054: Parent 1: LIBTEST_1.0 0x005c: Rev: 1 Flags: none Index: 4 Cnt: 2 Name: LIBTEST_2.0 0x0078: Parent 1: LIBTEST_1.5 ...
test_1找max,libtest.so.4中有max。
test_3找"max@LIBTEST_2.0 (2)",libtest.so.4中有max@@LIBTEST_2.0。
$ vi test_5.c
#include <stdio.h>
extern int max ( int a, int b );
int main ( int argc, char * argv[] ) { int ret = max( 1, 2 );
printf( "max() = %d\n", ret );
return( ret );
} /* end of main */
$ gcc -Wall -pipe -O0 -s -o test_5 test_5.c -L. -ltest $ LD_LIBRARY_PATH=. ./test_5 max_1( 1, 2 ) = 2 max() = 2
$ readelf -Ws test_5 | grep max 5: 00000000 0 FUNC GLOBAL DEFAULT UND max@LIBTEST_1.0 (3)
test_5找"max@LIBTEST_1.0 (3)",libtest.so.4中有max@LIBTEST_1.0。这个(3)不 知啥意思?
使用.symver时,max@@LIBTEST_2.0有两个@,即@@,表示这是缺省版本,一般对应当 前最新版本。假设动态链接库导出多个版本的max,主程序与之动态链接时使用缺省 版本的max,除非主程序中明确指定使用哪个版本的max。
假设动态链接库使用.symver导出多个版本的同名符号,缺省版本必不可少。
$ ls -l libtest.so lrwxrwxrwx 1 root root 12 Jul 24 18:09 libtest.so -> libtest.so.4* $ gcc -Wall -pipe -O0 -s -o test_6 test_1.c -L. -ltest $ LD_LIBRARY_PATH=. ./test_6 max_2( 1, 2, 134518692 ) = 134518692 max() = 134518692 $ readelf -Ws test_6 | grep max 1: 00000000 0 FUNC GLOBAL DEFAULT UND max@LIBTEST_2.0 (2)
test_6与test_1对应同一份源码(test_1.c),但链接时使用了不同的动态库,分别是 libtest.so.4与libtest.so.0,前者导出多个版本的max,后者只导出一个max。链接 结果出现差异,test_6导入max@LIBTEST_2.0(缺省版本),test_1导入max。此时 test_6的执行结果已经不是我们期望的结果,将不属于自己栈帧的栈中数据拿来参与 运算,幸运的是,max_2()是__cdecl调用风格,test_1.c中的extern声明确保不会出 现栈失衡,使得test_6正常结束。
$ objdump -dj .text test_1 | grep -B1 'call.*__libc_start_main' | awk '/push.0x/ { print $NF }'
$0x804854b
$ objdump -d test_1 | grep -A 11 "804854b:"
804854b: 8d 4c 24 04 lea 0x4(%esp),%ecx
804854f: 83 e4 f0 and $0xfffffff0,%esp
8048552: ff 71 fc pushl -0x4(%ecx)
8048555: 55 push %ebp
8048556: 89 e5 mov %esp,%ebp
8048558: 51 push %ecx
8048559: 83 ec 14 sub $0x14,%esp
804855c: 83 ec 08 sub $0x8,%esp
804855f: 6a 02 push $0x2
8048561: 6a 01 push $0x1
8048563: e8 b8 fe ff ff call 8048420 max@plt
8048568: 83 c4 10 add $0x10,%esp
$ objdump -dj .text test_6 | grep -B1 'call.__libc_start_main' | awk '/push.*0x/ { print $NF }'
前例说明,即使主程序源码相同、编译链接命令行相同,链接库升级仍要谨慎,尤其 是主程序需要重新编译的时候。
排查多版本同名符号导致的问题时,应该用"readelf -Ws",而不是"nm -D",因为后 者看不到同名符号的版本信息。
前面关于(2)、(3)的疑问,参看:
http://dev.gentoo.org/~dberkholz/articles/toolchain/symbol-versioning
好像对应vn_version成员,指出"Version of structure"。
$ readelf -Ws test_3 | grep max 1: 00000000 0 FUNC GLOBAL DEFAULT UND max@LIBTEST_2.0 (2) $ readelf -WV test_3 ... Version needs section '.gnu.version_r' contains 2 entries: Addr: 0x0000000008048300 Offset: 0x000300 Link: 6 (.dynstr) 000000: Version: 1 File: libc.so.6 Cnt: 1 0x0010: Name: GLIBC_2.0 Flags: none Version: 3 0x0020: Version: 1 File: libtest.so Cnt: 1 0x0030: Name: LIBTEST_2.0 Flags: none Version: 2 // (2)好像对应Version
$ readelf -Ws test_5 | grep max 5: 00000000 0 FUNC GLOBAL DEFAULT UND max@LIBTEST_1.0 (3) $ readelf -WV test_5 ... Version needs section '.gnu.version_r' contains 2 entries: Addr: 0x0000000008048300 Offset: 0x000300 Link: 6 (.dynstr) 000000: Version: 1 File: libtest.so Cnt: 1 0x0010: Name: LIBTEST_1.0 Flags: none Version: 3 // (3)好像对应Version 0x0020: Version: 1 File: libc.so.6 Cnt: 1 0x0030: Name: GLIBC_2.0 Flags: none Version: 2