在共享库中动态加载函数会导致分段错误

dynamically loading a function in a shared library causes a segmentation fault

我有这个简单的库

lib.h:

int lib()

lib.c:

#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 $@ $<

但是当我在应用程序中使用这个库时,在第 18 行调用 vkEnumerateInstanceLayerProperties 时出现段错误。

此外,如果我将名称 vkEnumerateInstanceLayerProperties 更改为其他名称,比如 test,那么一切正常并且(在我的系统中)打印 6。如果我根本不使用动态库,它也可以工作,即我直接编译 lib.cmain.c 而没有 -fPIC.

这是什么原因造成的,我该如何解决?

问题是这两个定义:

PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;

lib.so.

中定义名为 vkGetInstanceProcAddrvkEnumerateInstanceLayerProperties 全局 符号

这些定义覆盖 libvulkan 中的定义,因此 vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceLayerProperties"); 调用 return 定义 中的定义 lib.so,而不是 libvulcan.so.1 中预期的那个。并且该符号不可调用(在 .bss 部分),因此尝试调用它(自然地)会产生 SIGSEGV.

要解决此问题,请制作这些符号 static,或以不同的方式命名它们,例如p_vkGetInstanceProcAddrp_vkEnumerateInstanceLayerProperties.

更新:

Why compiling lib.c together with main.c directly (without an intermediate shared library inbetween) works?

因为符号(默认情况下)不会从动态符号 table 中的 executable 导出,除非 某些共享库引用它们。

您可以通过将 -Wl,--export-dynamic(这会导致主 executable 导出所有非本地符号)添加到主 executable link 来更改默认值线。如果这样做,linking lib.cmain.c 也会失败。

Also how can vkGetInstanceProcAddr"capture" thevkEnumerateInstanceLayerProperties` in lib.so?

通过使用正常的符号解析规则——第一个定义符号的 ELF 二进制文件获胜。

Shouldn't it just return some kind of predefined address that points to the correct function? I imagine that it is implemented with something like 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;
...

可以说这是一个实现错误——它应该 return 本地别名(..._internal 符号)的地址并且不受符号覆盖的影响。