我正在一个跨平台项目中,该项目由多个库组成,根据运行时条件动态地互相加载和卸载。当前,我观察到一个崩溃,这似乎是由于在使用dlclose()卸载共享库之前,其中一个共享库中的静态对象已被破坏而造成的。这似乎很奇怪,对我来说更像是个虫子。
为了研究这个问题,我创建了一个简单的项目,该项目包含三个源文件:main.cpp,lib1.cpp和lib2.cpp(分别用于可执行文件和两个库)。主可执行文件动态加载lib1,而lib1依次动态加载lib2。
main.cpp:
Logger mainGlobal("mainGlobal");
int main(int argc, char * argv[])
{
Logger mainFunction("mainFunction");
try
{
Logger mainTry("mainTry");
libutil::AutoLib lib("lib1");
lib.call("loadLib2");
}
catch (std::exception & e)
{
std::cerr << "Fatal: " << e.what() << std::endl;
}
std::cout << "Exiting main" << std::endl;
}
lib1.cpp:
Logger lib1Global("lib1Global");
std::auto_ptr<libutil::AutoLib> lib2;
DLL_EXPORT void loadLib2()
{
std::cout << "loadLib2" << std::endl;
lib2.reset(new libutil::AutoLib("lib2"));
}
lib2.cpp:
Logger lib2Global("lib2Global");
Logger
是一个简单的结构,只需登录其构造函数和析构函数即可。 libutil::AutoLib
是一个共享库加载器,它在其ctor中调用dlopen(path, RTLD_LAZY)
,在其dtor中调用dlclose()
,并允许调用从共享库导出的函数。这些类的代码很简单,但是如果需要,我也可以在这里发布。长话短说,如果我调用主可执行文件,则会看到以下日志:
mainGlobal ctor
mainFunction ctor
mainTry ctor
Loading library lib1.so
lib1Global ctor
dlopen(lib1.so) returned 0x14cd050
Library lib1.so loaded with handle 0x14cd050
Calling loadLib2 in library 0x14cd050
loadLib2
Loading library lib2.so
lib2Global ctor
dlopen(lib2.so) returned 0x14cd710
Library lib2.so loaded with handle 0x14cd710
Unloading library 0x14cd050
Calling dlclose(0x14cd050)
Library unloaded 0x14cd050
mainTry dtor
Exiting main
mainFunction dtor
lib2Global dtor
Unloading library 0x14cd710
Calling dlclose(0x14cd710)
Library unloaded 0x14cd710
lib1Global dtor
mainGlobal dtor
请注意
lib2Global dtor
行在Calling dlclose(0x14cd710)
行之前。所以问题是,这是错误还是正确的行为?
在SO中有一些问题,这些问题是在
dlclose()
之后没有销毁静态对象,但是我没有发现任何与相反情况有关的问题。我正在使用GCC 5.4.0-6ubuntu1〜16.04.10。
最佳答案
感谢戴维斯·赫林(Davis Herring)的提示,我找到了原因。因此在内存中保留了lib1.so
,但不允许将其卸载。事实证明,lib1.so
称为inline
函数,其中包含static const
变量,这使得gcc可以为此变量创建STB_GNU_UNIQUE绑定(bind)。即使lib1.so
随RTLD_LOCAL一起加载,也可以有效地使其无法加载。因此,要解决此问题,我可以从变量定义中删除static
限定符,或者从函数定义中删除inline
限定符,或者使用-fno-gnu-unique
g++标志。
完成此操作后,问题消失了:
mainGlobal ctor
mainFunction ctor
mainTry ctor
Loading library lib1.so
lib1Global ctor
dlopen(lib1.so) returned 0x1cfe050
Library lib1.so loaded with handle 0x1cfe050
Calling loadLib2 in library 0x1cfe050
loadLib2
Loading library lib2.so
lib2Global ctor
dlopen(lib2.so) returned 0x1cfe710
Library lib2.so loaded with handle 0x1cfe710
Unloading library 0x1cfe050
Calling dlclose(0x1cfe050)
Unloading library 0x1cfe710
Calling dlclose(0x1cfe710)
Library unloaded 0x1cfe710
lib1Global dtor
lib2Global dtor
Library unloaded 0x1cfe050
mainTry dtor
Exiting main
mainFunction dtor
mainGlobal dtor
以下是GNU GCC帮助的摘录:
-fno-gnu-unique
On systems with recent GNU assembler and C library, the C++ compiler
uses the STB_GNU_UNIQUE binding to make sure that definitions of template
static data members and static local variables in inline functions are unique
even in the presence of RTLD_LOCAL ; this is necessary to avoid problems
with a library used by two different RTLD_LOCAL plugins depending on a definition
in one of them and therefore disagreeing with the other one about the binding of
the symbol. But this causes dlclose to be ignored for affected DSOs; if your
program relies on reinitialization of a DSO via dlclose and dlopen , you can use
-fno-gnu-unique .
Here是带有相关问题的问题。