Oleg G
Oleg G

Reputation: 945

Threaded shared library for non threaded application

I have some application for which I need to write extension using shared library. In my shared library I need to use threads. And main application neither uses threads neither linked with threads library (libpthread.so, for example).

As first tests showed my library causes crashes of the main application. And if i use LD_PRELOAD hack crashes goes away:

LD_PRELOAD=/path/to/libpthread.so ./app

The only OS where i have no segfaults without LD_PRELOAD hack is OS X. On other it just crashes. I tested: Linux, FreeBSD, NetBSD.

My question is: is there a way to make my threaded shared library safe for non-threaded application without changing of the main application and LD_PRELOAD hacks?

To reproduce the problem i wrote simple example:

mylib.c

#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *_thread(void *arg) {
    int i;
    struct addrinfo *res;

    for (i=0; i<10000; i++) {
        if (getaddrinfo("localhost", NULL, NULL, &res) == 0) {
            if (res) freeaddrinfo(res);
        }
    }

    pthread_mutex_lock(&mutex);
    printf("Just another thread message!\n");
    pthread_mutex_unlock(&mutex);

    return NULL;
}

void make_thread() {
    pthread_t tid[10];
    int i, rc;

    for (i=0; i<10; i++) {
        rc = pthread_create(&tid[i], NULL, _thread, NULL);
        assert(rc == 0);
    }

    void *rv;
    for (i=0; i<10; i++) {
        rc = pthread_join(tid[i], &rv);
        assert(rc == 0);
    }
}

main.c

#include <stdio.h>
#include <dlfcn.h>

int main() {
    void *mylib_hdl;
    void (*make_thread)();

    mylib_hdl = dlopen("./libmy.so", RTLD_NOW);
    if (mylib_hdl == NULL) {
        printf("dlopen: %s\n", dlerror());
        return 1;
    }

    make_thread = (void (*)()) dlsym(mylib_hdl, "make_thread");
    if (make_thread == NULL) {
        printf("dlsym: %s\n", dlerror());
        return 1;
    }

    (*make_thread)();
    return 0;
}

Makefile

all:
    cc -pthread -fPIC -c mylib.c
    cc -pthread -shared -o libmy.so mylib.o
    cc -o main main.c -ldl

clean:
    rm *.o *.so main

And all together: https://github.com/olegwtf/sandbox/tree/bbbf76fdefe4bacef8a0de7a2475995719ae0436/threaded-so-for-non-threaded-app

$ make
cc -pthread -fPIC -c mylib.c
cc -pthread -shared -o libmy.so mylib.o
cc -o main main.c -ldl

$ ./main 
*** glibc detected *** ./main: double free or corruption (fasttop): 0x0000000001614c40 ***
Segmentation fault

$ ldd libmy.so | grep thr
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fe7e2591000)

$ LD_PRELOAD=/lib/x86_64-linux-gnu/libpthread.so.0 ./main
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!
Just another thread message!

Upvotes: 1

Views: 337

Answers (3)

Oleg G
Oleg G

Reputation: 945

gdb helped to understand what's goin on with this example.

After 3 tries gdb showed that app always crashed at rewind.c line 36 inside libc. Since tests were run on Debian 7, libc implementation is eglibc. And here you can see line 36 of rewind.c:
http://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_13/libc/libio/rewind.c?annotate=12752
_IO_acquire_lock() is a macros and after grepping eglibc source I found 2 places where it is defined:

Comment for first says Generic version and for second NPTL version, where NTPL is Native POSIX Thread Library. So in few words first defines non-threaded implementation for this and several other macroses and second threaded implementation.

When our main application is not linked with pthreads it starts and loads this first non-threaded implementation of _IO_acquire_lock() and others macroses. Then it opens our threaded shared library and executes function from it. And this function uses already loaded and non thread safe version of _IO_acquire_lock(). However in fact should use threads compatible version defined by pthreads. This is where segfault occures.

This is how it works on Linux. On *BSD situation is even more sad. On FreeBSD your program will hang up immediately after your threaded library will try to create new thread. On NetBSD instead of hang up program will be terminated with SIGABRT.

So answering to the main question: is it possible to use threaded shared library from application not linked with pthreads?
In general -- no. And particularly this depends on libc implementation. For OS X, for example, this will work without any problems. For Linux this will work if you'll not use libc functions that uses such special macroses redefined by pthreads. But how to know which uses? Ok, you can make 1+1, this looks safe. On *BSD your program will crash or hang up immediately, no matter what your thread do.

Upvotes: 0

Jens Gustedt
Jens Gustedt

Reputation: 78903

dlopen is supposed to do the right thing, and to open all the libraries your own .so depends upon.

In fact, your code is working for me if I comment out the address lookup code that you placed inside your thread function. So loading the pthread library works perfectly.

And if I run the code including the lookup, valgrind shows me that the crash is below getaddrinfo.

So the problem is not that the libraries aren't loaded, somehow their initialization code is not executed or not in the right order.

Upvotes: 1

John Zwinck
John Zwinck

Reputation: 249093

My question is: is there a way to make my threaded shared library safe for non-threaded application without changing of the main application and LD_PRELOAD hacks?

No, those are the two ways you can make it work. With neither in place, your program is invalid.

Upvotes: 1

Related Questions