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

Tav*_*nes 5 c++ java-native-interface rtti dlopen android-ndk

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

System.loadLibrary("bar");
Run Code Online (Sandbox Code Playgroud)

会做的伎俩.但由于它们是我自己的JAR附带的自定义库,我必须做类似的事情

System.load("/path/to/libfoo.so");
System.load("/path/to/libbar.so");
Run Code Online (Sandbox Code Playgroud)

libfoo需要先行,因为否则libbar无法找到它,因为它不在系统库搜索路径中.

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

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

foo.hpp

class foo { };

extern "C" const void* libfoo_foo_typeinfo();
Run Code Online (Sandbox Code Playgroud)

Foo.cpp中

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

extern "C" const void* libfoo_foo_typeinfo()
{
    return &typeid(foo);
}
Run Code Online (Sandbox Code Playgroud)

bar.cpp

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

extern "C" const void* libbar_foo_typeinfo()
{
    return &typeid(foo);
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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 $@
Run Code Online (Sandbox Code Playgroud)

在我的系统上,我得到了

$ make
...
$ ./main
false
true
Run Code Online (Sandbox Code Playgroud)

这是因为即使typeinfo地址不同,GCC的libstdc ++也使用受损的名称进行相等.例如,在LLVM的libc ++上,相等性基于typeinfo地址本身,因此我得到:

$ make CXX="clang++ -stdlib=libc++"
$ ./main
false
false
Run Code Online (Sandbox Code Playgroud)

如果我通过RTLD_GLOBAL,我明白了

true
true
Run Code Online (Sandbox Code Playgroud)

如果我编辑main.cpp首先加载libbar.so,它也可以工作,只要我告诉它它可以找到的地方libfoo.so:

$ LD_LIBRARY_PATH=. ./main
true
true
Run Code Online (Sandbox Code Playgroud)

但由于本文顶部描述的原因,这些都不是一个实际的解决方法.

这与https://github.com/android-ndk/ndk/issues/533非常类似,但是使用非动态类型,因此无法添加"关键功能"来强制typeinfo成为强符号.我碰巧在Android上首先重现了这个问题,但它不是特定于Android的.

Dan*_*ert 2

不,那是不可能的。RTLD_LOCAL试图完全防止这种情况发生,不幸的是,必须使用它,System.loadLibrary否则如果System.loadLibrary两个库各自定义不同的foo类,就会发生不好的事情。