是否可以在 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= 的库,将会发生不好的事情].
对于上下文:我有一个 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= 的库,将会发生不好的事情].