LINUX.ORG.RU

Значение переменной при динамической линковке и инициализаторе.

 ,


0

1

Дано:
Динамическая линковка с libxml2. Гружу символ xmlMalloc:

__typeof__(xmlMalloc) xmlMalloc;

static void _init_xmlMalloc(void) __attribute__((constructor));
static void _init_xmlMalloc(void) {
    void * obj = lok_library_func(library, "xmlMalloc");
    xmlMalloc = * (__typeof__(xmlMalloc) *)obj;
}

В lok_library_func обычный dlsym с проверкой на нашел\не нашел. При не нашел - падаем.

Грузит, находит по адресу. Но xmlMalloc == NULL. Т.е. переменная не присвоена. Окей, обращаемся к коду libxml:

#if defined(DEBUG_MEMORY_LOCATION) || defined(DEBUG_MEMORY)
<...>
xmlMallocFunc xmlMalloc = (xmlMallocFunc) xmlMemMalloc;
<...>
#else
<...>
/**
 * xmlMalloc:
 * @size:  the size requested in bytes
 *
 * The variable holding the libxml malloc() implementation
 *
 * Returns a pointer to the newly allocated block or NULL in case of error
 */
xmlMallocFunc xmlMalloc = malloc;

Точно известно, что DEBUG_MEMORY_LOCATION и DEBUG_MEMORY не установлены. Значит переменная должна быть инициализирована при загрузке библиотеки (что и происходит при dlopen).

Вопрос - почему переменная NULL?

Из-за того, что она NULL происходит падение при первом же выделении памяти внутри самой либы (например на LIBXML_TEST_VERSION).

★★★

Ответ на: комментарий от mittorn

dlopen при «открывании» библиотеки вызывает все ее конструкторы в порядки, назначенном линкером. На момент, когда вызывается dlsym конструктор целевой библиотеки уже вызван.

PPP328 ★★★ ()

Странно.

Собрал у себя минимальный пример:

gcc -o test_libxml `xml2-config --cflags` test_libxml2.c `xml2-config --libs`; strip test_libxml

Проблему не подтверждаю. Всё работает как должно. Скажу честно, я вообще слегка прифигел, как увидел Ваш вопрос.

Возможная проблема – а у Вас точно находится именно нужная либа? В некоторых дистрах, AFAIK, есть деление на runtime библиотеки и -dev библиотеки. Там может быть разница.

UPD.

Вопрос - почему переменная NULL?

Удалось хоть как-то воспроизвести проблему. Segfault, да. Я в xmlRegisterInputCallbacks() зарегистрировал функции обратного вызова с нулевыми потоками == NULL. Упало. Но я с таким не сталкивался на практике. Возможно что-то неправильно проинициализировано всё таки.

Moisha_Liberman ★★ ()
Последнее исправление: Moisha_Liberman (всего исправлений: 1)
Ответ на: Странно. от Moisha_Liberman

Что забавно, да, на MRE оно работает.

#include <libxml/xmlerror.h>
#include <libxml/tree.h>

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

__typeof__(xmlMalloc) xmlMalloc;

int main(void) {
	void * lib = dlopen("libxml2.so.2", RTLD_NOW);
	if (!lib)
		return 1;
	
	void * obj = dlsym(lib, "xmlMalloc");
	if (!obj)
		return 2;

	xmlMalloc = * (__typeof__(xmlMalloc) *)obj;

	printf("ptr = %p\n", xmlMalloc);
	return 0;
}

Вопрос в том, почему не работает в полноценном масштабе. Окей, будем смотреть очередь инициализации.

PPP328 ★★★ ()

Это не динамическая линковка а какойто велосипед. Тебе что, запретили линкером пользоваться?

УМВР на двух платформах.

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

Ах вот оно что!

Посмотрел Ваш код и не могу не согласиться с ранее высказавшемся оратором (уважаемым invy), выдвинувшим тезис о том, что у Вас получилась какая-то уж больно хитровы… круженная (ну, Вы поняли) динамическая линковка. =)))

Ладно, если уж без шуток, то всё понятно. Смотрите, вот корень проблем:

void * lib = dlopen("libxml2.so.2", RTLD_NOW);

Дело в том, что согласно man 3 dlopen,

RTLD_NOW If this value is specified, or the environment variable LD_BIND_NOW is set to a nonempty string, all undefined symbols in the shared object are resolved before dlopen() returns. If this cannot be done, an error is returned.

Т.е., пока не вернулись из dlopen(), все неотрезолвленные символы должны быть отрезолвлены. Если на момент загрузки либы там х.з. чё, то скорее всего и проинициализировано будет NULL.

Но тут срабатывает и ещё один момент. В libxml2 функция описана как attribute ((constructor)) же, верно? Но функции с таким аттрибутом не возвращают управление сразу:

Constructor routines are executed before dlopen returns (or before main() is started if the library is loaded at load time).

Такого рода функции должны проделать при загрузке некоторую работу (ну как минимум создать контекст разделяемой библиотеки в карте памяти процесса, который её вызывает и создать точку входа для него DT_INIT). Думаете, имеет смысл проверять инициализацию в таком коде? Там выставляется всё в NULL, а конкретные значения уже будут подставляться в run time. Собственно, это косвенно подтверждается тем, что я в рантайме во втором эксперименте вообще ничего не инициализировал. Т.е., сами по себе функции есть, а данных там NULL. Вот тогда тестовая прога и упала.

Да, так же не могу не согламиться с уважаемым invy:

Тебе что, запретили линкером пользоваться?

+1!

Moisha_Liberman ★★ ()