LINUX.ORG.RU

Разделяемая библиотека и thread safety

 ,


1

4

Предположим есть такая разделяемая библиотека, которая внутри себя использует треды. Для примера

#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<1; 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);
	}
}

И такая главная программа, которая использует библиотеку

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

int main() {
	void *mylib_hdl;
	void (*make_thread)();
	
	mylib_hdl = dlopen("./libmy.so", RTLD_NOW|RTLD_GLOBAL);
	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 -g -fPIC -c mylib.c
	cc -pthread -g -shared -o libmy.so mylib.o
	cc -g -o main main.c -ldl

clean:
	rm *.o *.so main

Заметьте главная программа скомпилирована без линковки с pthread, но при этом всё работает хорошо за счёт флага RTLD_GLOBAL для dlopen(). Если оставить только RTLD_NOW то будут сегфолты, т.к. внутри нашей библиотеки будут использованы не потокобезопасные версии функций libc, загруженные главной программой.

Но теперь поменяем главную программу немного

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

int main() {
	void *mylib_hdl;
	void (*make_thread)();
	
	struct protoent *proto = getprotobyname("tcp");
	
	mylib_hdl = dlopen("./libmy.so", RTLD_NOW|RTLD_GLOBAL);
	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;
}

В главной программе появился вызов getprotobyname() и теперь флаг RTLD_GLOBAL не помогает. В программу приходит сегфолт.

Как я понял этот вызов окончательно и безповоротно подгрузил непотокобезопасную часть libc и внутри библиотеки уже нет возможности использовать потокобезопасную версию.

Вопрос: можно ли таки победить сегфолт в последнем случаи? А если нельзя, то есть ли возможность внутри библиотеки как-то проверить, что будут использованы потокобезопасные функции и если нет, то завершиться, имея возможность оставить запись в лог или совершить другие предварительные действия?

В общем для моего случая сгодилась функция dl_iterate_phdr(). С помощью неё из библиотеки перед первым запуском или ещё как можно узнать какие сошки уже подгружены. Получается так, что если в главной программе был вызов getprotobyname(), то становится загруженной libnss_files.so, в которой собственно потом сегфолты и происходят. Т.е. мне достаточно убедиться, что такой библиотеки нет, а иначе завершиться с указанием причины.

В общем то подход весьма костыльный, но другого пока не найдено.

Olegymous ★★ ()
Ответ на: комментарий от Sorcerer

К сожалению не помогает, хотя судя по описанию должен бы

Olegymous ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.