如果我的应用程序加载(使用 dlopen)一个 .so 文件,该文件链接到 Boost 测试框架,我将无法卸载 so 文件。没有链接到 boost 似乎可以卸载它。
应用文件main.cpp:
#include <dlfcn.h>
#include <iostream>
int main()
{
auto sFileName = "./libtest_library.so";
auto handle = dlopen(sFileName, RTLD_LAZY | RTLD_LOCAL);
if (!handle)
std::cerr << "Error: " << dlerror() << std::endl;
auto closing = dlclose(handle);
while(1);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
库 .so 文件 (libtest_library.so):
#include <iostream>
//#include "boost/test/unit_test.hpp"
static void con() __attribute__((constructor));
static void dcon() __attribute__((destructor));
void con()
{
std::cout << "Constructing library..." << std::endl;
}
void dcon()
{
std::cout << "Destructing library..." << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
运行这个我得到输出:
Constructing library...
Destructing library...
Run Code Online (Sandbox Code Playgroud)
如果我在 libtest_library.so 中链接到 Boost 的单元测试框架,我只会得到 Constructing library...输出。dlclose(handle) 返回 0(成功)。
当前链接到 Boost v. 1.60.0,在 Ubuntu 14.04 上使用 gcc 5_2_0 进行编译。这是 Boost 中的错误吗?编译器?有任何想法吗?
我需要在一个项目中多次重新加载 .so 文件,并且需要完全卸载它(不存在于内存中)。我该如何解决这个问题?谢谢。
更新 1:
如果我只链接到 boost ,实际上调用了 libtest_library 析构函数,但没有卸载 boost_test_framework 库。但是,如果我包含“boost/test/unit_test.hpp”,则不会调用析构函数(libtest_library.so 拒绝卸载)。
更新 2:
查看 boost 的来源,我发现 boost 中有一个 C++ 单例导致了这个问题。
我可以在简化版本中复制该问题。基本上,如果我将以下单例添加到 libtest_library 它不起作用(无法卸载 .so 文件):
替代 1
class Singleton
{
public:
static Singleton & getInstance() { static Singleton instance; return instance; }
private:
Singleton() {}
~Singleton() {}
};
static Singleton & singleton = Singleton::getInstance();
Run Code Online (Sandbox Code Playgroud)
但是使用这个作品:
替代 2
class Singleton
{
public:
static Singleton & getInstance();
private:
Singleton() {}
~Singleton() {}
};
Singleton & Singleton::getInstance() { static Singleton instance; return instance; }
static Singleton & singleton = Singleton::getInstance();
Run Code Online (Sandbox Code Playgroud)
我尝试了不同的 GCC 编译器,结果相同。对我来说,这似乎是错误?
符号也有点不同:nm –C libtest_library.so | grep –i singleton我得到
替代 1(不工作):
0000000000201460 u guard variable for Singleton::getInstance()::instance
0000000000201458 b singleton
0000000000000e66 W Singleton::getInstance()
0000000000000f08 W Singleton::Singleton()
0000000000000f08 W Singleton::Singleton()
0000000000000f1c W Singleton::~Singleton()
0000000000000f1c W Singleton::~Singleton()
0000000000201468 u Singleton::getInstance()::instance
Run Code Online (Sandbox Code Playgroud)
和替代 2:
00000000002012f8 b guard variable for Singleton::getInstance()::instance
0000000000201300 b singleton
0000000000000bb0 T Singleton::getInstance()
0000000000000cec W Singleton::Singleton()
0000000000000cec W Singleton::Singleton()
0000000000000d00 W Singleton::~Singleton()
0000000000000d00 W Singleton::~Singleton()
0000000000201308 b Singleton::getInstance()::instance
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?
更新 3
我已经提取了 boost 中似乎产生问题的部分,并创建了一个演示问题的最小示例:
main_app.cpp - 主应用程序
#include <dlfcn.h>
#include <iostream>
int main()
{
for(auto i = 0; i < 2; i++) {
auto sFileName = "./libtest_library.so";
auto handle = dlopen(sFileName, RTLD_LAZY | RTLD_LOCAL);
if (!handle) {
printf("Dlerror: %s\n", dlerror());
continue;
}
auto closing = dlclose(handle);
printf("Dlerror: %s\n", dlerror());
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
main_lib.cpp - libtest_library.so
#include <iostream>
template<typename Derived>
class trivial_singleton_t {
public:
static Derived& instance() { static Derived the_inst; return the_inst; }
protected:
trivial_singleton_t() {}
~trivial_singleton_t() {}
};
class singleton_t : public trivial_singleton_t<singleton_t> {
private:
friend class trivial_singleton_t<singleton_t>;
singleton_t() {}
};
singleton_t & singleton = singleton_t::instance();
static void con() __attribute__((constructor));
static void dcon() __attribute__((destructor));
void con()
{
std::cout << "Constructing library..." << std::endl;
}
void dcon()
{
std::cout << "Destructing library..." << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
我得到以下输出:
Constructing library...
Dlerror: (null)
DLerror: (null)
Destructing library...
Run Code Online (Sandbox Code Playgroud)
因此,只有在 main 存在时才会卸载库。
如问题STB_GNU_UNIQUE中所述,编译后的二进制文件中有符号。
问题是使用这些符号并加载的库dlopen将被标记为NODELETE,因此在dlopen/dlclose调用之间持续存在。请参阅此处的第 445 行:http : //osxr.org : 8080/glibc/source/elf/dl-lookup.c
https://sourceware.org/binutils/docs/binutils/nm.html,STB_GNU_UNIQUE或u:
该符号是唯一的全球符号。这是标准 ELF 符号绑定集的 GNU 扩展。对于这样的符号,动态链接器将确保在整个过程中只有一个具有此名称和类型的符号在使用。
这些是在匿名命名空间中创建变量/方法或作为静态全局变量时创建的。
最快的解决方案是强制编译器不要像STB_GNU_UNIQUE使用链接器标志那样构建这些符号--no-gnu-unique。
不幸的是,这对我不起作用,因为我没有足够新的链接器,幸运的是我可以使用以下配置选项重建 gcc:--disable-gnu-unique-object. 记住还要使用链接器标志或新的 gcc 重建 boost 库。