代码之家  ›  专栏  ›  技术社区  ›  Tavian Barnes

是否可以跨本地加载的库合并弱符号,如vtables/typeinfo?

  •  5
  • Tavian Barnes  · 技术社区  · 8 年前

    对于上下文:我有一个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:

    水电站

    class foo { };
    
    extern "C" const void* libfoo_foo_typeinfo();
    

    食品cpp

    #include "foo.hpp"
    #include <typeinfo>
    
    extern "C" const void* libfoo_foo_typeinfo()
    {
        return &typeid(foo);
    }
    

    钢筋cpp

    #include "foo.hpp"
    #include <typeinfo>
    
    extern "C" const void* libbar_foo_typeinfo()
    {
        return &typeid(foo);
    }
    

    主.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;
    }
    

    生成文件

    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 U全球 相反,我明白了

    true
    true
    

    如果我编辑 main.cpp 装载 利巴.so 首先,如果我告诉它在哪里可以找到,它也可以工作 李福索 :

    $ LD_LIBRARY_PATH=. ./main
    true
    true
    

    但基于本文顶部描述的原因,这两个都不是一个切实可行的解决办法。

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

    1 回复  |  直到 8 年前
        1
  •  2
  •   Dan Albert    8 年前

    不,那是不可能的。 RTLD_LOCAL 试图阻止这一切,不幸的是 System.loadLibrary 因为否则坏事情会发生如果你 系统.loadLibrary 两个库各自定义不同的 foo 上课。