符号表和链接选项 的关系

最近工作中常遇到符号表的问题,发现它与链接选项有着很密切关系,本文将借助一个实例,具体讲解一下,涉及到的概念包括动态链接(-l)、动态加载(dlopen)、全局动态符号表等。

一.讨论APP + dlopen一个so的情况
1.APP中的全局变量symbol是否导出

测试一:ldtest1】
main.cpp】:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <ctype.h>
  4. #include <unistd.h>
  5. #include <dlfcn.h>
  6. #include <cassert>
  7. #include <errno.h>typedef void (*func1) (void);int abc = -1;int main ( void )
  8. {
  9.   void *testlib;
  10.   func1 testing_call;
  11.   fprintf( stderr, "abc = %d\n", abc );
  12.   //是NOW还是LAZY,对于符号表没有影响
  13.   //if ( ( testlib = dlopen( "./testlib.so", RTLD_NOW | RTLD_GLOBAL ) ) != NULL )
  14.   if ( ( testlib = dlopen( "./testlib.so", RTLD_LAZY | RTLD_GLOBAL ) ) != NULL )
  15.   {
  16.     testing_call = (func1 )dlsym( testlib, "testing" );
  17.     assert ( testing_call );    ( *testing_call )();
  18.     fprintf( stderr, "abc = %d\n", abc );
  19.   }
  20.   else
  21.   {
  22. fprintf( stderr, "dlopen error:  %s\n", dlerror() );
  23.   }
  24.   return( 0 );
  25. }
复制代码

testlib.cpp 】:
  1. extern int abc;extern "C" {
  2. void testing ( void )
  3. {
  4.   abc = 0;
  5.   return;}
  6. }
复制代码

Makefile 】:
  1. t = test testlib.so
  2. #这两个选项是一个意思
  3. #LDMODULE = -Wl,-E
  4. LDMODULE = -rdynamicall: $t
  5. clean:
  6. rm -f $t coretestlib.so: testlib.cpp
  7.   g++ -I. -shared -g -o $@ testlib.cpptest: main.cpp
  8.   g++ -I. -L. ${LDMODULE}  main.cpp -g -o $@ -ldl
复制代码

如果未加-Wl,-E 选项,则dlopen打开时会抱怨symbol abc找不着,打开失败。

2.APP中的函数名是否导出
测试二:ldtest2
main.cpp 】:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <ctype.h>
  4. #include <unistd.h>
  5. #include <dlfcn.h>
  6. #include <cassert>
  7. #include <errno.h>typedef void (*func1) (void);int abc = -1;void func()
  8. {
  9.   printf("hi, func in main\n");
  10. }int main ( void )
  11. {
  12.   void *testlib;
  13.   func1 testing_call;
  14.   fprintf( stderr, "abc = %d\n", abc );
  15.   //if ( ( testlib = dlopen( "./testlib.so", RTLD_NOW | RTLD_GLOBAL ) ) != NULL )
  16.   if ( ( testlib = dlopen( "./testlib.so", RTLD_LAZY | RTLD_GLOBAL ) ) != NULL )
  17.   {
  18.     testing_call = (func1 )dlsym( testlib, "testing" );
  19.     assert ( testing_call );    ( *testing_call )();
  20.     fprintf( stderr, "abc = %d\n", abc );
  21.   }
  22.   else
  23.   {
  24. fprintf( stderr, "dlopen error:  %s\n", dlerror() );
  25.   }
  26.   return( 0 );
  27. }
复制代码

testlib.cpp 】:
  1. extern int abc;void func()
  2. {
  3.   printf("hi, func in so\n");
  4. }extern "C" {
  5. void testing ( void )
  6. {
  7.   func();
  8.   abc = 0;
  9.   return;}
  10. }
复制代码

Makefile 】:
  1. t = test testlib.so
  2. LDMODULE = -rdynamic
  3. SHARE = -Wl,-Bsymbolicall: $t
  4. clean:
  5. rm -f $t coretestlib.so: testlib.cpp
  6.   g++ -I. -shared ${SHARE} -g -o $@ testlib.cpptest: main.cpp
  7.   g++ -I. -L. ${LDMODULE}  main.cpp -g -o $@ -ldl
复制代码

如果 编译 主程序是带有rdynamic选项,则symbol func确实会导出,具体表现就是testing函数中会调用main.cpp中的func(),那么,如何告诉编译器调用testlib.cpp中的func呢,在编译so时加上-Wl,-Bsymbolic选项,表示绑定自己的symbol。

3.如果链接上一个动态库,APP中的函数名是否导出
如果不带rdynamic选项,symbol func不会导出,所以如果testlib.cpp中没有定义func(),则上面的ldtest2会报错:
dlopen error:  ./testlib.so: undefined symbol: _Z4funcv。
testlib.cpp 】:
  1. #include <stdio.h>extern void func();extern "C" {
  2. void testing ( void )
  3. {
  4.   func();
  5.   return;
  6. }
  7. }
复制代码

参考一下LD手册:
--export-dynamic
           When creating a dynamically linked executable, add all  symbols  to the  dynamic symbol table.  The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time. If you do not use this option, the dynamic symbol table  will  normally contain  only  those  symbols  which  are referenced by some dynamic object mentioned in the link.
           如果你没有用这个选项,那么动态符号表里面将只会包含动态链接对象引用过的符号,如果没有其他的动态链接对象,则什么也不包含。

上面的例子中,编译主程序时并未链接任何动态库,所以是后一种情况,下面我们作一个试验,人为链上一个库:
【测试:ldtest22】
main.cpp 】:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <ctype.h>
  4. #include <unistd.h>
  5. #include <dlfcn.h>
  6. #include <cassert>
  7. #include <errno.h>typedef void (*func1) (void);int abc = -1;void func()
  8. {
  9.   printf("hi, func in main\n");
  10. }int main ( void )
  11. {
  12.   void *testlib;
  13.   func1 testing_call;
  14.   fprintf( stderr, "abc = %d\n", abc );
  15.   //if ( ( testlib = dlopen( "./testlib.so", RTLD_NOW | RTLD_GLOBAL ) ) != NULL )
  16.   if ( ( testlib = dlopen( "./testlib.so", RTLD_LAZY | RTLD_GLOBAL ) ) != NULL )
  17.   {
  18.     testing_call = (func1 )dlsym( testlib, "testing" );
  19.     assert ( testing_call );    ( *testing_call )();
  20.     fprintf( stderr, "abc = %d\n", abc );
  21.   }
  22.   else
  23.   {
  24. fprintf( stderr, "dlopen error:  %s\n", dlerror() );
  25.   }
  26.   return( 0 );
  27. }
复制代码

testlib.cpp 】:
  1. #include <stdio.h>extern void func();extern "C" {
  2. void testing ( void )
  3. {
  4.   func();
  5.   return;
  6. }
  7. }
复制代码

sharelib.h 】:
#include <stdio.h>
void share();

sharelib.cpp 】:
  1. #include "sharelib.h"extern void func();void share()
  2. {
  3.   printf("share:\n");
  4.   //此处如果调用了func,则对应的符号将从执行文件中导出;
  5.   //否则,即便前面声明了extern func(),也不会导出符号来
  6.   func();
  7. }
复制代码

Makefile 】:
  1. t = test testlib.so libsharelib.soall: $t
  2. clean:
  3.   rm -f $t corelibsharelib.so: sharelib.cpp
  4.   g++ -I. -L. -shared -g -o $@ sharelib.cpp
  5. testlib.so: testlib.cpp
  6.   g++ -I. -shared ${SHARE} -g -o $@ testlib.cpptest: main.cpp libsharelib.so
  7.   g++ -I. -L. ${LDMODULE}  main.cpp -g -o $@ -ldl -lsharelib
复制代码

此处编译主程序并未带有rdynamic选项,但symbol func依然会导出,主要原因就在于它动态链上一个共享库libsharelib.so,而在该库中引用了func()这个符号,使得其自动被导出。
          
二.讨论APP link一个so的情况
1.APP中的全局变量symbol是否导出
【测试三:ldtest3】
main.cpp 】:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <ctype.h>
  4. #include <unistd.h>
  5. #include <dlfcn.h>
  6. #include <cassert>
  7. #include <errno.h>
  8. #include "testlib.h"int abc = -1;int main ( void )
  9. {
  10.   fprintf( stderr, "abc = %d\n", abc );
  11.   testing();
  12.   fprintf( stderr, "dlopen error:  %s\n", dlerror() );
  13.   return( 0 );
  14. }
复制代码

testlib.h 】:
void testing();

testlib.cpp 】:
  1. #include "testlib.h"
  2. extern int abc;void testing ( void )
  3. {
  4.   abc = 0;
  5.   return;}
复制代码

Makefile 】:
  1. t = test libtestlib.soall: $t
  2. clean:
  3. rm -f $t corelibtestlib.so: testlib.cpp
  4.   g++ -I. -shared -g -o $@ testlib.cpptest: main.cpp libtestlib.so
  5.   g++ -I. -L. ${LDMODULE}  main.cpp -g -o $@ -ldl -ltestlib
复制代码

运行时设置LD_LIBRARY_PATH为当前路径即可
我们并未设置任何特殊编译选项,可见采用-ltestlib的方式,APP中的符号表对so是可见的。

2.APP中的函数名是否导出
测试四:ldtest4
main.cpp 】:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <ctype.h>
  4. #include <unistd.h>
  5. #include <dlfcn.h>
  6. #include <cassert>
  7. #include <errno.h>int abc = -1;void func()
  8. {
  9.   printf("hi, func in main\n");
  10. }int main ( void )
  11. {
  12.   fprintf( stderr, "abc = %d\n", abc );
  13.   testing();
  14.   fprintf( stderr, "abc = %d\n", abc );
  15.   return( 0 );
  16. }
复制代码

testlib.h 】:
void testing();

testlib.cpp 】:
  1. #include "testlib.h"
  2. extern int abc;void func()
  3. {
  4.   printf("hi, func in so\n");
  5. }void testing ( void )
  6. {
  7.   func();
  8.   abc = 0;
  9.   return;}
复制代码

Makefile 】:
  1. t = test libtestlib.so
  2. SHARE = -Wl,-Bsymbolicall: $t
  3. clean:
  4. rm -f $t corelibtestlib.so: testlib.cpp
  5.   g++ -I. -shared ${SHARE} -g -o $@ testlib.cpptest: main.cpp libtestlib.so
  6.   g++ -I. -L. ${LDMODULE}  main.cpp -g -o $@ -ldl -ltestlib
复制代码

symbol func确实会导出,具体表现就是testing函数中会调用main.cpp中的func(),那么,如何告诉编译器调用testlib.cpp中的func呢,在编译so时加上-Wl,-Bsymbolic选项,表示绑定自己的symbol。

三.LD选项说明
在前面测试中,我们谈到了诸如Wl,-E和-Wl,-Bsymbolic之类的选项,这里作一个统一说明:
Wl   表示后面的选项将被作为参数传给连接器LD
-E   等价于--export-dynamic,表示将执行程序中的符号表全部导出
-Bsymbolic   当创建动态库时,如果某一个符号在全局符号表中已经存在,强制采用本地的;而缺省情况下,本地定义会被覆盖。这一点,对于保证多个动态库之间互不影响是很重要的,起码,它保证了本地定义的优先性,不会在程序员不知情的时候,偷偷用外部定义换掉。此外,如果想保证某一个动态库不会影响其他的动态库,则可以采用后面谈到的version-script选项;
--version-script   通过version脚本来控制动态库中的符号是global,还是local的;

我们修改一下ldtest2:
Makefile 】:
  1. t = test testlib.so
  2. #LDMODULE = -Wl,-E
  3. LDMODULE = -rdynamic
  4. SHARE = -Wl,-Bsymbolic -Wl,--version-script,versionall: $t
  5. clean:
  6.   rm -f $t coretestlib.so: testlib.cpp
  7.   g++ -I. -shared ${SHARE} -g -o $@ testlib.cpptest: main.cpp
  8.   g++ -I. -L. ${LDMODULE}  main.cpp -g -o $@ -ldl
复制代码

version 】:
VERS {
global:
  testing;
local: *;
};

我们同时采用了Bsymbolic和--version-script选项,在version script中指明,只有testing这一个符号将会被加入到动态符号表中,这样,就能够保证dlsym可以找到testing,并且load该动态库不会增添新的无用符号,因为前面local设的是通配符"*"。

四.几个相关命令
nm -C -D libtest.so
把libtest.so中的所有动态符号用demagle的形式打印出来

objdump -T libtest.so
同上,不过更详细一些
其中,-D选项特别有用,针对version-script选项,只打印global标志的symbol。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值