我有一些应用程序,我需要使用共享库编写扩展。在我的共享库中,我需要使用线程。并且主应用程序既不使用既未与线程库链接的线程(例如libpthread.so)。
首次测试显示我的库导致主应用程序崩溃。如果我使用LD_PRELOAD,黑客崩溃就会消失:
LD_PRELOAD=/path/to/libpthread.so ./app
我没有没有LD_PRELOAD黑客的段错误的唯一操作系统是OS X.另一方面,它只是崩溃。我测试过:Linux,FreeBSD,NetBSD。
我的问题是:有没有办法让我的线程共享库对非线程应用程序安全,而无需更改主应用程序和LD_PRELOAD hacks?
为了重现这个问题,我写了一个简单的例子:
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;
}
生成文件
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
$ 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!
答案 0 :(得分:1)
我的问题是:有没有办法让我的线程共享库安全 用于非线程应用程序而无需更改主应用程序 和LD_PRELOAD黑客攻击?
不,这两种方法可以让它发挥作用。如果两者都没有,那么您的程序无效。
答案 1 :(得分:1)
dlopen
应该做正确的事情,并打开你自己的.so
所依赖的所有库。
事实上,如果我注释掉你放在线程函数中的地址查找代码,那么你的代码对我有用。因此加载pthread库非常有效。
如果我运行包含查找的代码,valgrind会告诉我崩溃低于getaddrinfo
。
所以问题不在于没有加载库,不知道他们的初始化代码是否以正确的顺序执行或不执行。
答案 2 :(得分:0)
gdb帮助理解了这个例子的用途。
经过3次尝试,gdb显示应用程序总是在libc内的rewind.c第36行崩溃。由于测试是在Debian 7上运行的,因此libc实现是eglibc。在这里你可以看到rewind.c的第36行:
http://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_13/libc/libio/rewind.c?annotate=12752
_IO_acquire_lock()
是一个宏,在点击eglibc源后,我找到了2个定义它的地方:
评论首先说Generic version
和第二NPTL version
,其中NTPL是Native POSIX Thread Library。因此,首先为此以及其他几个宏和第二个线程实现定义非线程实现。
当我们的主应用程序没有与pthreads链接时,它会启动并加载_IO_acquire_lock()
和其他宏的第一个非线程实现。然后它打开我们的线程共享库并从中执行函数。此函数使用已加载且非线程安全的_IO_acquire_lock()
版本。但实际上应该使用由pthreads定义的线程兼容版本。这就是segfault发生的地方。
这是它在Linux上的工作原理。关于* BSD的情况更令人难过。在FreeBSD上你的程序将在你的线程库尝试创建新线程后立即挂起。在NetBSD而不是挂断程序将使用SIGABRT终止。
回答主要问题:是否可以使用未与pthreads链接的应用程序的线程共享库?
一般来说 - 没有。特别是这取决于libc的实现。例如,对于OS X,这将毫无问题地工作。对于Linux,如果您不使用使用由pthreads重新定义的特殊宏的libc函数,这将起作用。但是如何知道哪些用途?好的,你可以做1 + 1,这看起来很安全。在* BSD上,无论你的主题是什么,你的程序都会立即崩溃或挂起。