是否可以在 RTLD_LOCAL 加载的库中合并像 vtables/typeinfo 这样的弱符号?

Is it possible to merge weak symbols like vtables/typeinfo across RTLD_LOCAL'ly loaded libraries?

对于上下文:我有一个 Java 项目,该项目部分使用两个 JNI 库实现。例如,libbar.so 依赖于 libfoo.so。如果这些是系统库,

System.loadLibrary("bar");

就可以了。但由于它们是我随 JAR 一起运送的自定义库,所以我必须做类似

的事情
System.load("/path/to/libfoo.so");
System.load("/path/to/libbar.so");

libfoo 需要先走,否则 libbar 找不到它,因为它不在系统库搜索路径中。

这已经运行了一段时间,但我现在 运行 遇到了一个问题,即 std::any_cast 抛出 std::bad_any_cast 尽管类型是正确的。我追踪到这两个库对该类型的类型信息有不同的定义,并且它们在 运行 时间没有被合并。这似乎是因为 System.load() 最终使用 RTLD_LOCAL 而不是 RTLD_GLOBAL.

调用 dlopen()

我写这个是为了演示不需要 JNI 的行为:

foo.hpp

class foo { };

extern "C" const void* libfoo_foo_typeinfo();

foo.cpp

#include "foo.hpp"
#include <typeinfo>

extern "C" const void* libfoo_foo_typeinfo()
{
    return &typeid(foo);
}

bar.cpp

#include "foo.hpp"
#include <typeinfo>

extern "C" const void* libbar_foo_typeinfo()
{
    return &typeid(foo);
}

main.cpp

#include <iostream>
#include <typeinfo>
#include <dlfcn.h>

int main() {
    void* libfoo = dlopen("./libfoo.so", RTLD_NOW | RTLD_LOCAL);
    void* libbar = dlopen("./libbar.so", RTLD_NOW | RTLD_LOCAL);

    auto libfoo_fn = reinterpret_cast<const void* (*)()>(
        dlsym(libfoo, "libfoo_foo_typeinfo"));
    auto libbar_fn = reinterpret_cast<const void* (*)()>(
        dlsym(libbar, "libbar_foo_typeinfo"));

    auto libfoo_ti = static_cast<const std::type_info*>(libfoo_fn());
    auto libbar_ti = static_cast<const std::type_info*>(libbar_fn());

    std::cout << std::boolalpha
              << (libfoo_ti == libbar_ti) << "\n"
              << (*libfoo_ti == *libbar_ti) << "\n";
    return 0;
}

Makefile

all: libfoo.so libbar.so main

libfoo.so: foo.cpp
        $(CXX) -fpic -shared -Wl,-soname=$@ $^ -o $@

libbar.so: bar.cpp
        $(CXX) -fpic -shared -Wl,-soname=$@ $^ -L. -lfoo -o $@

main: main.cpp
        $(CXX) $^ -ldl -o $@

在我的系统上,我得到

$ make
...
$ ./main
false
true

这是因为即使类型信息地址不同,GCC 的 libstdc++ 也使用错位的名称来实现相等性。例如,在 LLVM 的 libc++ 上,相等性基于类型信息地址本身,所以我得到:

$ make CXX="clang++ -stdlib=libc++"
$ ./main
false
false

如果我通过 RTLD_GLOBAL,我会看到

true
true

如果我编辑 main.cpp 以首先加载 libbar.so,它也可以工作,前提是我告诉它在哪里可以找到 libfoo.so:

$ LD_LIBRARY_PATH=. ./main
true
true

但由于本 post 顶部所述的原因,这些都不是实际的解决方法。

这与 https://github.com/android-ndk/ndk/issues/533 非常相似,但具有非动态类型,因此无法添加 "key function" 来强制类型信息成为强符号。我刚好在 Android 上重现了这个问题,但它不是 Android 特定的。

不,那是不可能的。 RTLD_LOCAL 试图阻止这种情况,不幸的是必须用于 System.loadLibrary,否则如果你 System.loadLibrary 两个定义不同 foo [=16= 的库,将会发生不好的事情].