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

时间:2018-08-09 17:34:39

标签: c++ android-ndk java-native-interface rtti dlopen

对于上下文:我有一个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。我追溯到以下事实:两个库对该类型的typeinfo都有不同的定义,并且它们在运行时没有合并。这似乎是因为System.load()最终以dlopen()而不是RTLD_LOCAL调用了RTLD_GLOBAL

我编写此代码是为了演示这种行为而无需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

这是因为即使typeinfo地址不同,GCC的libstdc ++也会使用乱码来实现相等。例如,在LLVM的libc ++中,相等性基于typeinfo地址本身,因此我得到:

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

如果我通过RTLD_GLOBAL,我会看到

true
true

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

$ LD_LIBRARY_PATH=. ./main
true
true

但是由于本文开头所述的原因,这些都不是可行的解决方法。

这与https://github.com/android-ndk/ndk/issues/533非常相似,但是具有非动态类型,因此无法添加“键函数”以强制typeinfo成为强符号。我碰巧首先在Android上重现了该问题,但这不是特定于Android的。

1 个答案:

答案 0 :(得分:2)

否,那是不可能的。 RTLD_LOCAL试图防止这种情况的发生,不幸的是,System.loadLibrary必须使用System.loadLibrary,因为否则,如果您foo两个都定义了不同的group by类的库,则会发生不好的事情。