2
votes

This is the scenario:

I have an application (main.exe) which dynamically loads a library libA.so using dlopen(). libA.so has dependency on another library libB.so.

Now libB.so has a constructor that spawns a thread (in detached state) and blocks on reading from a named pipe.

What happens to the thread , when libA.so is unloaded using dlclose() - (i assume this will unload libB.so as well)?.

I get segmentation fault in the thread after the dlclose(libA.so).

Pseudo code:

main.c (main.exe):

handle = dlopen(libA.so)
// function calls
dlclose(handle)

libA.so depends on 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
    }
2

2 Answers

4
votes

Unloading a shared library which is still in use is Undefined Behaviour. Calling dlclose() on a handle is a declaration that neither the functions nor the data objects provided through that handle are still required.

There is no way for dlclose() to verify this fact. If you still have a pointer to a function or data object in the loadable module, those pointers become invalid and may not be used. If there is a pointer into a loaded function on the call stack of any thread -- which will be the case if the thread is waiting on a file descriptor -- then that thread may not return into that call frame. (Longjmp to a prior frame might work, if the stack can be unwound without reference to the unloaded module. That will likely work in C, but throwing a C++ exception to escape from an unloaded function is likely to fail.

2
votes

Congratulations - you've rediscovered that a non-nop dlclose implementation is fundamentally unsafe. The C language makes no provision for code or pseudo-static-storage data whose lifetime is anything other than the whole lifetime of the program, and in general library code cannot be safely removed since there are all sorts of ways that references to it may have leaked and still be reachable. You've actually found an exceptionally good example; common implementations attempt to catch and "fix" leaks via atexit and such by running the handlers at dlclose time, but there doesn't seem to be any way to catch and fix a thread left running.

As a workaround, there is a special ELF flag you can set by passing -Wl,-z,nodelete when linking the shared library libB.so (or libA.so if that's more convenient, e.g. if you don't have control over how libB.so is linked) that will prevent it from being unloaded by dlclose. Unfortunately, this design is backwards. Unloading is fundamentally unsafe unless a library is specifically written to be safe against unloading, so the default should be "nodelete" with an explicit option required to make unloading possible. Unfortunately there's little chance this will ever be fixed.

Another way to prevent unloading is have a constructor call dlopen on itself to leak a reference, so that the reference count will always be positive and dlclose will do nothing.