小心两个共享库共用同一个静态库
小心两个共享库共用同一个静态库.pdf
下载测试代码:x.zip(和帖子的略不同,x.zip包中的全局变量是个类对象,带有构造和析构函数)
推荐阅读:http://blog.chinaunix.net/uid-20682147-id-351108.html(Linux上制作可执行的共享库示例)
问1:如果测试中的全局变量global_var是个带构造和析构的类对象,会如何?(答案在最后)
问2:如果使用-fPIE替代-fPIC编译链接,会是什么结果了?
位置无关代码(PIC)对常量和函数入口地址的操作都是采用基于基寄存器(base register)BASE+ 偏移量的相对地址的寻址方式,即使程序被装载到内存中的不同地址(即 BASE值不同),而偏移量是不变的,所以程序仍然可以找到正确的入口地址或者常量。
为何要小心?原因是在使用dlopen动态加载共享库时,如果静态库中包含有全局变量,可能会出现名同地址不同的全局变量。
解决办法:总是使用RTLD_GLOBAL加载共享库,而不是RTLD_LOCAL。以下是测试程序:
Makefile
- # test shared libraries use static a same static library
- # the global variables defined at static library have the same address
- all: x libshared_lib1.so libshared_lib2.so
- x: x.cpp #libstatic_lib.a #libshared_lib1.so #libshared_lib2.so
- g++ -g -o $@ $^ -ldl
- libstatic_lib.a: static_lib.h static_lib.cpp
- g++ -g -fPIC -c static_lib.cpp -I.
- ar cr $@ static_lib.o
- libshared_lib1.so: shared_lib1.cpp libstatic_lib.a
- g++ -g -fPIC -shared -o $@ $^ -I.
- libshared_lib2.so: shared_lib2.cpp libstatic_lib.a
- g++ -g -fPIC -shared -o $@ $^ -I.
- clean:
- rm -f static_lib.o libstatic_lib.a
- rm -f shared_lib1.o libshared_lib1.so
- rm -f shared_lib2.o libshared_lib2.so
- rm -f x
测试程序x.cpp
-
#include <dlfcn.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
extern void call_foo(const char* name, int load_flag);
-
int main()
- {
-
int flag = RTLD_GLOBAL|RTLD_NOW;
// 如果是RTLD_GLOBAL则静态库中定义的全局变量在共享库中名同地址也同
-
//int flag = RTLD_LOCAL|RTLD_NOW;
// 如果是RTLD_LOCAL则静态库中定义的全局变量在共享库中名同地址不同
-
-
call_foo("./libshared_lib1.so", flag);
-
call_foo("./libshared_lib2.so", flag);
-
-
return 0;
-
}
-
-
// RTLD_NOW
-
// RTLD_LAZY
-
// RTLD_GLOBAL
-
// RTLD_LOCAL
-
void call_foo(const char* name, int load_flag)
-
{
-
char *error;
-
void (*foo)();
-
-
void* handle = dlopen(name, load_flag);
-
if (NULL == handle)
-
{
-
fprintf (stderr, "%s\n", dlerror());
-
exit(1);
-
}
-
-
dlerror(); /* Clear
any existing error */
-
*(void **) (&foo) = dlsym(handle, "foo");
-
if ((error = dlerror()) != NULL)
-
{
-
fprintf (stderr, "%s\n", error);
-
exit(1);
-
}
-
-
(*foo)();
- }
静态库头文件static_lib.h
- extern int global_var;
静态库实现文件static_lib.cpp
-
#include <stdio.h>
- int global_var = 2013;
第1个共享库实现文件shared_lib1.cpp
-
#include "static_lib.h"
-
#include <stdio.h>
-
-
extern "C" void foo()
-
{
-
global_var = 1111;
-
printf("%p 1-> %d\n", &global_var, global_var);
- }
第2个共享库实现文件shared_lib2.cpp
-
#include "static_lib.h"
-
#include <stdio.h>
-
-
extern "C" void foo()
-
{
-
printf("%p 2-> %d\n", &global_var, global_var);
- }
测试环境:
x86_64 x86_64 GNU/Linux 2.6.16
附:
如果你想覆盖系统调用,可以使用LD_PRELOAD或/etc/ld.so.preload,也可进一步了解RTLD_NEXT
答:结果是即使以RTLD_GLOBAL方式加载,都会出现两次构造和析构调用,如果是RTLD_GLOBAL方式,地址仍然相同,也就是同一个对象执行了两次构造和析构,后果当然是非常危险。运行测试代码x.zip即可得到验证。
- 如果被依赖的不是静态库,而是共享库,则无论何种方式都不存在问题
- 为何即使RTLD_GLOBAL加载,也会执行两次构造和析构?原因是两个共享库存在相同的代码段,如果被依赖的是共享库,则不存在这个问题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义