这是场景:
我有一个应用程序(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
b.c(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
}
答案 0 :(得分:4)
卸载仍在使用的共享库是未定义的行为。在句柄上调用dlclose()
是一个声明,即不需要通过该句柄提供的函数或数据对象。
dlclose()
无法验证这一事实。如果在可加载模块中仍具有指向函数或数据对象的指针,则这些指针将变为无效,并且可能无法使用。如果任何线程的调用堆栈上都有一个指向已加载函数的指针(如果线程正在等待文件描述符,就是这种情况),则该线程可能不会返回该调用框架。 (如果可以在不引用卸载模块的情况下将堆栈展开,则将前一帧长时间工作可能会起作用。这可能会在C语言中起作用,但是抛出C ++异常以从卸载函数中转义可能会失败。
答案 1 :(得分:2)
恭喜-您已经重新发现非nop dlclose
实现基本上是不安全的。 C语言不提供任何代码或伪静态存储数据,这些数据或伪静态存储数据的生命周期不是程序的整个生命周期,而且一般来说,无法安全地删除库代码,因为对它的引用可能有多种方式泄漏并且仍然可以到达。实际上,您已经找到了一个非常好的例子;常见的实现尝试通过atexit
捕获并“修复”泄漏,例如在dlclose
时运行处理程序,但是似乎没有任何方法可以捕获并修复正在运行的线程。 / p>
作为一种解决方法,在链接共享库-Wl,-z,nodelete
时可以通过传递libB.so
来设置特殊的ELF标志(如果更方便,例如,如果您不愿意,可以通过libA.so
来设置)无法控制libB.so
的链接方式),以防止dlclose
卸载它。不幸的是,这种设计是倒退的。除非从本质上讲是为了防止卸载而专门编写的库,否则卸载从根本上是不安全的,因此默认值应为“ nodelete”,并带有使卸载成为可能的显式选项。不幸的是,几乎不可能解决这个问题。
防止卸载的另一种方法是在其自身上进行构造函数调用dlopen
以泄漏引用,这样引用计数将始终为正,dlclose
将不执行任何操作。