有了动态链接和共享目标文件的加持,Linux提供了一种更加灵活的模块加载方式:通过提供dlopen,dlsym,dlclose,dlerror几个API,可以实现在运行的时候动态加载模块,从而实现插件的功能。
如下代码演示了动态加载Add函数的过程,add.cpp按照正常编译“g++ -fPIC –shared –o libadd.so add.cpp”成libadd.so,main.cpp通过“g++ main.cpp -ldl”编译为a.out。main.cpp中首先通过dlopen接口取得一个句柄void *handle,然后通过dlsym从句柄中查找符号Add,找到后将其转化为Add函数,然后就可以按照正常的函数使用,最后dlclose关闭句柄,期间有任何错误可以通过dlerror来获取。
问题四:静态全局变量与动态库导致double free在全面了解了动态链接相关知识后,我们来看一个静态全局变量和动态库纠结在一起引发的问题,代码如下,foo.cpp中有一个静态全局对象foo_,foo.cpp会编译成一个libfoo.a,bar.cpp依赖libfoo.a库,它本身会编译成libbar.so,main.cpp既依赖于libfoo.a又依赖libbar.so。
编译的makefile如下:
运行a.out会导致double free的错误。这是由于在一个位置上调用了两次析构函数造成的。之所以会这样是因为链接的时候先链接的静态库,将foo_的符号解析为静态库中的全局变量,当动态链接libbar.so时,由于全局已经有符号foo_,因此根据全局符号介入,动态库中对foo_的引用会指向静态库中版本,导致最后在同一个对象上析构了两次。
解决办法如下:
1. 不使用全局对象
2. 编译时候调换库的顺序,动态库放在前面,这样全局只会有一个foo_对象
3. 全部使用动态库
4. 通过编译器参数来控制符号的可见性。
总结:通过四个编译链接中碰到的问题,基本把编译链接的这些事覆盖了一遍,有了这些基础,在日常工作中应对一般的编译链接问题应该可以做到游刃有余。由于篇幅有限,文章省略了大量的细节,主要集中在大的框架原理性梳理,如果想进一步深挖相关的细节,可参与相关参考文献,以及阅读elf.h相关的头文件。
参考文献:1. 《链接器和加载器》
2. 《深入理解计算机系统》
3. 《程序员的自我修养》