динамическая загрузка функции в общей библиотеке вызывает ошибку сегментации
У меня есть эта простая библиотека
lib.h
:
int lib()
:
#include <stdio.h>
#include <dlfcn.h>
#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>
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(&count, NULL);
printf("%d\n", count);
return 0;
}
Я компилирую его в разделяемую библиотеку, используя
libabc.so: lib.o
$(CC) -shared -o $@ $^ -ldl
lib.o: lib.c lib.h
$(CC) -fPIC -g -Wall -c -o $@ $<
Но когда я использую эту библиотеку в приложении, я получаю segfault при вызове в строке 18.
Более того, если я изменю имя
vkEnumerateInstanceLayerProperties
к чему-то другому, скажем
test
, то все работает нормально и (в моей системе)
6
печатается. Это также работает, если я вообще не использую динамическую библиотеку, т.е. компилирую
lib.c
вместе с
main.c
прямо без
-fPIC
.
Что вызывает это и как мне решить эту проблему?
1 ответ
Проблема в том, что эти два определения:
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
определить глобальные символы с именем
vkGetInstanceProcAddr
а также
vkEnumerateInstanceLayerProperties
в .
Эти определения переопределяют те, что внутри
libvulkan
, и поэтому
vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceLayerProperties");
вызов возвращает определение внутри
lib.so
, вместо предполагаемого внутри
libvulcan.so.1
. И этот символ не вызывается (находится в
.bss
section), поэтому попытка вызвать его (естественно) приводит к
SIGSEGV
.
Чтобы исправить это, либо сделайте эти символы
static
, или назовите их по-другому, например
p_vkGetInstanceProcAddr
а также
p_vkEnumerateInstanceLayerProperties
.
Обновлять:
Почему компиляция lib.c вместе с main.c напрямую (без промежуточной разделяемой библиотеки между ними) работает?
Поскольку символы (по умолчанию) не экспортируются из исполняемого файла в таблицу динамических символов, если на них не ссылается какая-либо общая библиотека.
Вы можете изменить значение по умолчанию, добавив
-Wl,--export-dynamic
(что заставляет основной исполняемый файл экспортировать все нелокальные символы) в строку ссылки основного исполняемого файла. Если вы это сделаете, связывая
lib.c
с
main.c
также потерпит неудачу.
Также как vkGetInstanceProcAddr
"capture" the
vkEnumerateInstanceLayerProperties` в lib.so?
Используя обычные правила разрешения символов, выигрывает первый двоичный файл ELF, определяющий символ.
Разве он не должен просто возвращать какой-то предопределенный адрес, указывающий на правильную функцию? Я предполагаю, что это реализовано с чем-то вроде
if (!strcmp(...)) return vkGetInstanceProcAddr_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
символ) и быть невосприимчивым к переопределению символов.