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

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

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. И этот символ не вызывается (находится в .bsssection), поэтому попытка вызвать его (естественно) приводит к SIGSEGV.

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

Обновлять:

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

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

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

Также как vkGetInstanceProcAddr "capture" thevkEnumerateInstanceLayerProperties` в 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символ) и быть невосприимчивым к переопределению символов.

Другие вопросы по тегам