16.29 导出符号的版本控制 https://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 int max ( 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 */ -------------------------------------------------------------------------- $ vi test_1.c -------------------------------------------------------------------------- #include 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 -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 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 */ __asm__ \ (" \ .symver max_0,max@LIBTEST_1.0 ;\ .symver max_2,max@@LIBTEST_2.0 ;\ "); -------------------------------------------------------------------------- $ vi test_2.map -------------------------------------------------------------------------- LIBTEST_1.0 { global : max; local : *; }; LIBTEST_2.0 { global : max; } LIBTEST_1.0; -------------------------------------------------------------------------- $ vi test_3.c -------------------------------------------------------------------------- #include 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 ); } /* end of main */ -------------------------------------------------------------------------- $ 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 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; } LIBTEST_1.5; -------------------------------------------------------------------------- $ 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 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 */ __asm__ \ (" \ .symver max,max@LIBTEST_1.0 ;\ "); -------------------------------------------------------------------------- $ 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 8048568: 83 c4 10 add $0x10,%esp $ objdump -dj .text test_6 | grep -B1 'call.*__libc_start_main' | awk '/push.*0x/ { print $NF }' $0x80484db $ objdump -d test_6 | grep -A 11 "80484db:" 80484db: 8d 4c 24 04 lea 0x4(%esp),%ecx 80484df: 83 e4 f0 and $0xfffffff0,%esp 80484e2: ff 71 fc pushl -0x4(%ecx) 80484e5: 55 push %ebp 80484e6: 89 e5 mov %esp,%ebp 80484e8: 51 push %ecx 80484e9: 83 ec 14 sub $0x14,%esp 80484ec: 83 ec 08 sub $0x8,%esp 80484ef: 6a 02 push $0x2 80484f1: 6a 01 push $0x1 80484f3: e8 a8 fe ff ff call 80483a0 80484f8: 83 c4 10 add $0x10,%esp 前例说明,即使主程序源码相同、编译链接命令行相同,链接库升级仍要谨慎,尤其 是主程序需要重新编译的时候。 排查多版本同名符号导致的问题时,应该用"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