динамическая загрузка функции в общей библиотеке вызывает ошибку сегментации

#c #vulkan #dynamic-linking #dynamic-loading #libdl

#c #vulkan #динамическое соединение #динамическая загрузка #либдл

Вопрос:

У меня есть эта простая библиотека

lib.h :

 int lib()  

lib.c :

 #include lt;stdio.hgt;  #include lt;dlfcn.hgt;  #define VK_NO_PROTOTYPES #include lt;vulkan/vulkan.hgt;  PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;  int lib() {  void *lib = dlopen("libvulkan.so.1", RTLD_NOW);  vkGetInstanceProcAddr = dlsym(lib, "vkGetInstanceProcAddr");   vkEnumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties)vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceLayerProperties");  uint32_t count;  vkEnumerateInstanceLayerProperties(amp;count, NULL);  printf("%dn", count);   return 0; }  

Я компилирую его в общую библиотеку, используя

 libabc.so: lib.o  $(CC) -shared -o $@ $^ -ldl  lib.o: lib.c lib.h  $(CC) -fPIC -g -Wall -c -o $@ $lt;  

Но когда я использую эту библиотеку в приложении, я получаю segfault при vkEnumerateInstanceLayerProperties вызове в строке 18.

Более того, если я vkEnumerateInstanceLayerProperties , скажем , изменю имя на что-то другое test , то все будет работать просто отлично и (в моей системе) 6 будет напечатано. Это также работает, если я вообще не использую динамическую библиотеку, т. Е. Я компилирую lib.c вместе с main.c напрямую без -fPIC .

Что является причиной этого и как мне это решить?

Комментарии:

1. Когда вы динамически связываете функции, каково значение vkEnumerateInstanceLayerProperties после получения указателя функции из библиотеки DLL vulkan?

2. Отчеты GDB (PFN_vkEnumerateInstanceLayerProperties) 0x7ffff7fc4050 lt;vkEnumerateInstanceLayerPropertiesgt;

3. А как насчет статической компиляции? Просто любопытно посмотреть, есть ли существенная разница в адресе.

4. (PFN_vkEnumerateInstanceLayerProperties) 0x7ffff7da1810 lt;vkEnumerateInstanceLayerPropertiesgt;

Ответ №1:

Проблема в том, что эти два определения:

 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;  

определите глобальные символы с именем vkGetInstanceProcAddr и vkEnumerateInstanceLayerProperties в lib.so .

Эти определения переопределяют определения внутри libvulkan , и поэтому vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceLayerProperties"); вызов возвращает определение внутри lib.so , а не предполагаемое внутри libvulcan.so.1 . И этот символ не может быть вызван (находится в .bss разделе), поэтому попытка вызвать его (естественно) приводит к a SIGSEGV .

Чтобы исправить это, либо сделайте эти символы static , либо назовите их по-другому, например p_vkGetInstanceProcAddr , и p_vkEnumerateInstanceLayerProperties .

Обновить:

Почему компиляция lib.c вместе с main.c напрямую (без промежуточной общей библиотеки между ними) работает?

Поскольку символы (по умолчанию) не экспортируются из исполняемого файла в таблице динамических символов, если только на них не ссылается какая-либо общая библиотека.

Вы можете изменить значение по умолчанию, добавив -Wl,--export-dynamic (что заставляет основной исполняемый файл экспортировать все нелокальные символы) в основную строку ссылки на исполняемый файл. Если вы сделаете это, связь lib.c с main.c также завершится ошибкой.

Также как vkGetInstanceProcAddr vkEnumerateInstanceLayerProperties может в "capture" the lib.so?

Используя обычные правила разрешения символов-выигрывает первый двоичный файл ELF, определяющий символ.

Разве он не должен просто возвращать какой — то заранее определенный адрес, который указывает на правильную функцию? Я представляю, что это реализовано с помощью чего-то подобного if (!strcmp(...)) return vkGetInstanceProcAddr_internal .

Если бы это было реализовано таким образом, это бы сработало.

Реализация, которую я могу найти, не выполняет ..._internal свою роль:

 void *globalGetProcAddr(const char *name) {  if (!name || name[0] != 'v' || name[1] != 'k') return NULL;   name  = 2;  if (!strcmp(name, "CreateInstance")) return vkCreateInstance;  if (!strcmp(name, "EnumerateInstanceExtensionProperties")) return vkEnumerateInstanceExtensionProperties; ...  

Возможно, это ошибка реализации-он должен возвращать адрес локального псевдонима ( ..._internal символа) и быть невосприимчивым к переопределению символов.

Комментарии:

1. Почему компиляция lib.c вместе с main.c прямой (без промежуточной общей библиотеки между ними) работает? В таком случае, почему vkGetInstanceProcAddr возвращается правильный адрес? Также как можно vkGetInstanceProcAddr «захватить» vkEnumerateInstanceLayerProperties в lib.so? Разве он не должен просто возвращать какой — то заранее определенный адрес, который указывает на правильную функцию? Я представляю, что это реализовано с помощью чего-то вроде if (!strcmp(...)) return vkGetInstanceProcAddr_internal

2. @JimMorrison Я обновил ответ.