这是场景:
我有一个应用程序(main.exe(,它使用 dlopen(( 动态加载库 libA.so。 libA.so 依赖于另一个库 libB.so。
现在 libB.so 有一个构造函数,它生成一个线程(处于分离状态(并在从命名管道读取时阻塞。
当使用 dlclose(( 卸载 libA.so 时线程会发生什么 - (我认为这也将卸载 libB.so(?
我在 dlclose(libA.so( 之后的线程中出现分段错误。
伪代码:
main.c (main.exe):
handle = dlopen(libA.so)
// function calls
dlclose(handle)
libA.so 取决于 libB.so
公元前 (libB.so(:
__attribute_constructor__void start() {
pthread_create(ThreadFunction)
void ThreadFunction() {
while(1) {
fd = open("path_to_pipe", READONLY)
read(fd, buffer, size)
//Process the content
}
卸载仍在使用的共享库是未定义的行为。在句柄上调用dlclose()
是一种声明,表明仍然不需要通过该句柄提供的函数和数据对象。
dlclose()
没有办法核实这一事实。如果可加载模块中仍有指向函数或数据对象的指针,则这些指针将变为无效,并且可能无法使用。如果在任何线程的调用堆栈上有一个指向已加载函数的指针(如果线程正在等待文件描述符,则会出现这种情况(,则该线程可能不会返回到该调用帧中。(Longjmp 到前一帧可能会起作用,如果堆栈可以在不引用卸载模块的情况下展开。这在 C 中可能有效,但抛出C++异常以从卸载的函数中转义可能会失败。
恭喜 - 您重新发现非 nopdlclose
实现从根本上是不安全的。C 语言没有提供代码或伪静态存储数据,其生存期不是程序的整个生命周期,并且通常库代码无法安全地删除,因为对它的引用可能已经泄露并且仍然可以访问的各种方式。你实际上已经找到了一个非常好的例子;常见的实现试图通过atexit
等dlclose
时间运行处理程序来捕获和"修复"泄漏,但似乎没有任何方法可以捕获和修复正在运行的线程。
作为一种解决方法,您可以通过在链接共享库时传递-Wl,-z,nodelete
来设置一个特殊的 ELF 标志libB.so
(或者libA.so
如果这样更方便,例如,如果您无法控制libB.so
的链接方式(,这将防止它被dlclose
卸载。不幸的是,这种设计是倒退的。卸载从根本上是不安全的,除非专门编写库以防止卸载,因此默认值应为"nodelete",并需要显式选项才能使卸载成为可能。不幸的是,这几乎没有机会得到解决。
防止卸载的另一种方法是在自身上有一个构造函数调用dlopen
来泄漏引用,以便引用计数始终为正数,dlclose
将不执行任何操作。