Chinaunix首页 | 论坛 | 博客
  • 博客访问: 216364
  • 博文数量: 76
  • 博客积分: 106
  • 博客等级: 民兵
  • 技术积分: 170
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-04 16:55
文章分类

全部博文(76)

文章存档

2014年(47)

2013年(2)

2012年(20)

2011年(7)

分类: C/C++

2014-12-30 21:41:40

小心两个共享库共用同一个静态库.pdf

注:以下内容仅针对Linux/GCC环境,不涵盖Windows,包括Cygwin环境。

下载测试代码:x.zip和帖子的略不同,x.zip包中的全局变量是个类对象,带有构造和析构函数
推荐阅读:http://blog.chinaunix.net/uid-20682147-id-351108.htmlLinux上制作可执行的共享库示例


问1:如果测试中的全局变量global_var是个带构造和析构的类对象,会如何?(答案在最后
问2:如果使用-fPIE替代-fPIC编译链接,会是什么结果了?


位置无关代码(PIC)对常量和函数入口地址的操作都是采用基于基寄存器(base register)BASE+ 偏移量的相对地址的寻址方式,即使程序被装载到内存中的不同地址(即 BASE值不同),而偏移量是不变的,所以程序仍然可以找到正确的入口地址或者常量。

为何要小心?原因是在使用dlopen动态加载共享库时,如果静态库中包含有全局变量,可能会出现名同地址不同的全局变量。
解决办法:总是使用RTLD_GLOBAL加载共享库,而不是RTLD_LOCAL。以下是测试程序:

Makefile
  1. # test shared libraries use static a same static library
  2. # the global variables defined at static library have the same address
  3. all: x libshared_lib1.so libshared_lib2.so
  4. x: x.cpp #libstatic_lib.a #libshared_lib1.so #libshared_lib2.so
  5. g++ -g -o $@ $^ -ldl
  6. libstatic_lib.a: static_lib.h static_lib.cpp
  7. g++ -g -fPIC -c static_lib.cpp -I.
  8. ar cr $@ static_lib.o
  9. libshared_lib1.so: shared_lib1.cpp libstatic_lib.a
  10. g++ -g -fPIC -shared -o $@ $^ -I.
  11. libshared_lib2.so: shared_lib2.cpp libstatic_lib.a
  12. g++ -g -fPIC -shared -o $@ $^ -I.
  13. clean:
  14. rm -f static_lib.o libstatic_lib.a
  15. rm -f shared_lib1.o libshared_lib1.so
  16. rm -f shared_lib2.o libshared_lib2.so
  17. rm -f x

测试程序x.cpp
  1. #include <dlfcn.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>

  4. extern void call_foo(const char* name, int load_flag);
  5. int main()
  6. {
  7.         int flag = RTLD_GLOBAL|RTLD_NOW;  // 如果是RTLD_GLOBAL则静态库中定义的全局变量在共享库中名同地址也同
  8.         //int flag = RTLD_LOCAL|RTLD_NOW;  // 如果是RTLD_LOCAL则静态库中定义的全局变量在共享库中名同地址
  9.                 
  10.         call_foo("./libshared_lib1.so", flag);
  11.         call_foo("./libshared_lib2.so", flag);

  12.         return 0;
  13. }

  14. // RTLD_NOW
  15. // RTLD_LAZY
  16. // RTLD_GLOBAL
  17. // RTLD_LOCAL
  18. void call_foo(const char* name, int load_flag)
  19. {
  20.         char *error;
  21.         void (*foo)();

  22.         void* handle = dlopen(name, load_flag);
  23.         if (NULL == handle)
  24.         {
  25.                 fprintf (stderr, "%s\n", dlerror());
  26.                 exit(1);
  27.         }

  28.         dlerror(); /* Clear any existing error */
  29.         *(void **) (&foo) = dlsym(handle, "foo");
  30.         if ((error = dlerror()) != NULL)
  31.         {
  32.                 fprintf (stderr, "%s\n", error);
  33.                 exit(1);
  34.         }

  35.         (*foo)();
  36. }

静态库头文件static_lib.h
  1. extern int global_var;

静态库实现文件static_lib.cpp
  1. #include <stdio.h>
  2. int global_var = 2013;

第1个共享库实现文件shared_lib1.cpp 
  1. #include "static_lib.h"
  2. #include <stdio.h>

  3. extern "C" void foo()
  4. {
  5.         global_var = 1111;
  6.         printf("%p 1-> %d\n", &global_var, global_var);
  7. }

第2个共享库实现文件shared_lib2.cpp
  1. #include "static_lib.h"
  2. #include <stdio.h>

  3. extern "C" void foo()
  4. {
  5.         printf("%p 2-> %d\n", &global_var, global_var);
  6. }

测试环境:
x86_64 x86_64 GNU/Linux 2.6.16

附:
1)如果你想覆盖系统调用,可以使用LD_PRELOAD或/etc/ld.so.preload,也可进一步了解RTLD_NEXT;
2)静态库顺序关系:假设X.a依赖Z.a,则顺序为X.a Z.a,亦即被依赖的排在后面,否则链接时会报某些符号找不到(详细请参见:链接静态库的顺序问题)。

答:结果是即使以RTLD_GLOBAL方式加载,都会出现两次构造和析构调用,如果是RTLD_GLOBAL方式,地址仍然相同,也就是同一个对象执行了两次构造和析构,后果当然是非常危险。运行测试代码x.zip即可得到验证。



段表(Section Table) 一个描述文件中各个段的数组
.code/.text 代码段
.data 段保存的是那些已经初始化了的全局静态变量和局部静态变量
.rodata/.rodata1 段存放的是只读数据,一般是程序里面的只读变量(如const修饰的变量)和字符串常量
.bss 段存放的是未初始化的全局变量和局部静态变量
.plt/.got 段动态链接的跳转表和全局入口表
.symtab 符号表(Symbol Table)
.strtab 字符串表(String Table),用于存储ELF文件中用到的各种字符串
.init/.fini 程序初始化与终结代码段
.note 额外的编译器信息。比如程序的公司名、发布版本号等
.line 调试时的行号表,即源代码行号与编译后指令的对应表
.hash 符号哈希表
.dynamic 动态链接信息
.debug 调试信息
.comment 存放的是编译器版本信息,比如字符串:”GCC: (GNU) 4.2.0”
自定义段 GCC提供了一个扩展机制,使得程序员可以指定变量所处的段:
1.__attribute__((section("FOO"))) int global = 42;
2.__attribute__((section("BAR"))) void foo()
{
}
在全局变量或函数之前加上"__attribute__((section("name")))"属性就可以把相应的变量或函数放到以"name"作为段名的段中。

  • 如果被依赖的不是静态库,而是共享库,则无论何种方式都不存在问题
  • 为何即使RTLD_GLOBAL加载,也会执行两次构造和析构?原因是两个共享库存在相同的代码段,如果被依赖的是共享库,则不存在这个问题


  1. -Wl的使用
  2. -Wl表示后面的参数传递给链接器,其中l是linker的意思。
  3. 链接时指定共享库的搜索路径(类似于设置LD_LIBRARY_PATH):
  4. -Wl,-rpath=/usr/local/abc:/data/abc
  5. 以上也可以分开写:
  6. -Wl,-rpath=/usr/local/abc -Wl,-rpath=/data/abc
  7. 部分库链接它的静态库,部分库链接它的共享库:
  8. -Wl,-static -lb -Wl,-call_shared -la -lz
  9. 指定链接器:
  10. -Wl,-dynamic-linker /lib/ld-linux.so.2 -e _so_start
  11. 指定导出的符号:
  12. -Wl,--export-dynamic,--version-script,exports.lds
  13. exports.lds的格式可以为:
  14. {
  15. global:
  16. foo;
  17. };
  18. 指定共享库的soname:
  19. -Wl,--export-dynamic,--version-script,exports.lds,-soname=libqhttpd.so
  20. -rpath 增加共享库搜索路径
  21. --retain-symbols-file表示不丢弃未定义的符号和需要重定位的符号
  22. --export-dynamic 创建一个动态连接的可执行程序时, 把所有的符号加到动态符号表中


阅读(964) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~