即使在成功加载所需的目标文件后加载 JNI 依赖项时在 Java 中出现 UnsatisfiedLinkError(未定义符号)
Getting an UnsatisfiedLinkError (undefined symbol) in Java while loading JNI dependencies even after successfully loading the required object file
我正在为一个项目使用 Google OR-tools 库 (v6.4)(尽管我的问题并非特定于该库)。这由一个 jar 组成,它具有一些本机依赖项(一堆“.so”/“.dylib”目标文件,取决于 OS)。我项目的构建是在 Ubuntu 14.04
上进行的
我面临的问题:尝试在运行时加载特定目标文件(使用 System.load()
)时,我收到消息为 "undefined symbol" 的 UnsatisfiedLinkError(我已经添加了下面的堆栈跟踪)。但是,我正在加载在此之前定义此符号的目标文件,因此我不确定为什么会抛出此错误。
我正在通过以下方式加载依赖项:目标文件被打包到 Maven 在构建期间创建的 jar 中,并在运行时被提取和加载(使用 System.load()
)。方法如下:
public class EnvironmentUtils {
public static void loadResourceFromJar(String prefix, String suffix) {
String tempFilesDirectory = System.getProperty("java.io.tmpdir");
File tempFile = null;
try {
tempFile = new File(tempFilesDirectory + "/" + prefix + suffix);
tempFile.deleteOnExit();
try (final InputStream inputStream = EnvironmentUtils.class.getClassLoader().
getResourceAsStream(prefix+suffix)) {
if (inputStream == null) {
throw new RuntimeException(prefix + suffix + " was not found inside JAR.");
} else {
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
System.load(tempFile.getAbsolutePath());
} catch (Exception e) {
//Log top 10 lines of stack trace
}
}
}
在所有依赖项的静态块内调用此方法:
public class DummyClass {
static {
String sharedLibraryExtension = EnvironmentUtils.getSharedLibraryExtension(); //.so for linux, .dylib for Mac
String jniLibraryExtension = EnvironmentUtils.getJniLibraryExtension(); //.so for linux, .jnilib for Mac
EnvironmentUtils.loadResourceFromJar("libfap", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libcvrptw_lib", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libortools", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libdimacs", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libjniortools", jniLibraryExtension);
}
}
On 运行 System.load()
for libdimacs.so,抛出一个 UnsatisfiedLinkError。堆栈跟踪:
java.lang.UnsatisfiedLinkError: /tmp/libdimacs.so: /tmp/libdimacs.so: undefined symbol: _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at com.(PROJECT_NAME).utils.EnvironmentUtils.loadResourceFromJar(EnvironmentUtils.java:78)
at com.(PROJECT_NAME).DummyClass.<clinit>(DummyClass.java:28)
但是,这个符号“_ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_”出现在 libortools.so 中,它在 libdimacs 之前加载。我通过 运行 以下命令验证了这一点:
objdump -t (LIBRARY_PATH)/libortools.so | grep _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
这给了我以下输出:
0000000000ce12cc gw F .text 00000091 _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
所以看起来符号应该在 System.load()
调用时定义,除非在加载包含的目标文件时出现问题。为了检查目标文件是否已正确加载,我使用了 this solution 中详述的方法。除了该答案中详述的 class 之外,我在 EnvironmentUtils.loadResourceFromJar()
中的 System.load()
调用之后添加了以下行以打印最近加载的库名称:
public class EnvironmentUtils {
public static void loadResourceFromJar(String prefix, String suffix) {
...
System.load(tempFile.getAbsolutePath());
final String[] libraries = ClassScope.getLoadedLibraries(ClassLoader.getSystemClassLoader());
System.out.println(libraries[libraries.length - 1]);
}
}
输出(直到 UnsatisfiedLinkError 之前)如下:
/tmp/libfap.so
/tmp/libcvrptw_lib.so
/tmp/libortools.so
所以 libortools.so 似乎加载正确,这意味着符号应该加载到内存中。完全相同的代码与相应的 Mac (".dylib") 依赖项完美配合(构建于 MacOS Sierra 10.12.5)。对于解决此问题的任何建议,我们将不胜感激。谢谢。
很抱歉 java 工件目前可能已损坏...
您可以使用 c++filt 来分解符号 ;)
c++filt _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
google::FlagRegisterer::FlagRegisterer<bool>(char const*, char const*, char const*, bool*, bool*)
事实上,gflag 最近将其命名空间从 google::
更改为 gflags::
和 glog 或 protobobuf?尝试找到正确的,但我猜它失败了...
注意:仍然不完全确定谁是使用 google::
命名空间的坏人,因为 libortools 合并了它的所有静态依赖项,但我想现在你明白了这个错误......
note2: 我在 mizux/shared 分支中有一个补丁 https://github.com/google/or-tools/commit/805bc0600f4b5645114da704a0eb04a0b1058e28#diff-e8590fe6fb5044985c8bf8c9e73c0d88R114
警告:该分支当前已损坏且尚未准备就绪。对于 unix,我正在尝试从静态依赖项转变为动态依赖项,因此我需要修复所有 rpath、传递性 deps 等......并且在这个过程中我还必须解决这个问题(我在使用时没有重现静态依赖)
如果完成时间太长(我们应该在 2018 年 5 月底之前创建一个版本 6.7.2 或 6.8(即新工件)),它可能只包含此修复程序而不是我的分支...
我正在为一个项目使用 Google OR-tools 库 (v6.4)(尽管我的问题并非特定于该库)。这由一个 jar 组成,它具有一些本机依赖项(一堆“.so”/“.dylib”目标文件,取决于 OS)。我项目的构建是在 Ubuntu 14.04
上进行的我面临的问题:尝试在运行时加载特定目标文件(使用 System.load()
)时,我收到消息为 "undefined symbol" 的 UnsatisfiedLinkError(我已经添加了下面的堆栈跟踪)。但是,我正在加载在此之前定义此符号的目标文件,因此我不确定为什么会抛出此错误。
我正在通过以下方式加载依赖项:目标文件被打包到 Maven 在构建期间创建的 jar 中,并在运行时被提取和加载(使用 System.load()
)。方法如下:
public class EnvironmentUtils {
public static void loadResourceFromJar(String prefix, String suffix) {
String tempFilesDirectory = System.getProperty("java.io.tmpdir");
File tempFile = null;
try {
tempFile = new File(tempFilesDirectory + "/" + prefix + suffix);
tempFile.deleteOnExit();
try (final InputStream inputStream = EnvironmentUtils.class.getClassLoader().
getResourceAsStream(prefix+suffix)) {
if (inputStream == null) {
throw new RuntimeException(prefix + suffix + " was not found inside JAR.");
} else {
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
System.load(tempFile.getAbsolutePath());
} catch (Exception e) {
//Log top 10 lines of stack trace
}
}
}
在所有依赖项的静态块内调用此方法:
public class DummyClass {
static {
String sharedLibraryExtension = EnvironmentUtils.getSharedLibraryExtension(); //.so for linux, .dylib for Mac
String jniLibraryExtension = EnvironmentUtils.getJniLibraryExtension(); //.so for linux, .jnilib for Mac
EnvironmentUtils.loadResourceFromJar("libfap", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libcvrptw_lib", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libortools", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libdimacs", sharedLibraryExtension);
EnvironmentUtils.loadResourceFromJar("libjniortools", jniLibraryExtension);
}
}
On 运行 System.load()
for libdimacs.so,抛出一个 UnsatisfiedLinkError。堆栈跟踪:
java.lang.UnsatisfiedLinkError: /tmp/libdimacs.so: /tmp/libdimacs.so: undefined symbol: _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at com.(PROJECT_NAME).utils.EnvironmentUtils.loadResourceFromJar(EnvironmentUtils.java:78)
at com.(PROJECT_NAME).DummyClass.<clinit>(DummyClass.java:28)
但是,这个符号“_ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_”出现在 libortools.so 中,它在 libdimacs 之前加载。我通过 运行 以下命令验证了这一点:
objdump -t (LIBRARY_PATH)/libortools.so | grep _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
这给了我以下输出:
0000000000ce12cc gw F .text 00000091 _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
所以看起来符号应该在 System.load()
调用时定义,除非在加载包含的目标文件时出现问题。为了检查目标文件是否已正确加载,我使用了 this solution 中详述的方法。除了该答案中详述的 class 之外,我在 EnvironmentUtils.loadResourceFromJar()
中的 System.load()
调用之后添加了以下行以打印最近加载的库名称:
public class EnvironmentUtils {
public static void loadResourceFromJar(String prefix, String suffix) {
...
System.load(tempFile.getAbsolutePath());
final String[] libraries = ClassScope.getLoadedLibraries(ClassLoader.getSystemClassLoader());
System.out.println(libraries[libraries.length - 1]);
}
}
输出(直到 UnsatisfiedLinkError 之前)如下:
/tmp/libfap.so
/tmp/libcvrptw_lib.so
/tmp/libortools.so
所以 libortools.so 似乎加载正确,这意味着符号应该加载到内存中。完全相同的代码与相应的 Mac (".dylib") 依赖项完美配合(构建于 MacOS Sierra 10.12.5)。对于解决此问题的任何建议,我们将不胜感激。谢谢。
很抱歉 java 工件目前可能已损坏...
您可以使用 c++filt 来分解符号 ;)
c++filt _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
google::FlagRegisterer::FlagRegisterer<bool>(char const*, char const*, char const*, bool*, bool*)
事实上,gflag 最近将其命名空间从 google::
更改为 gflags::
和 glog 或 protobobuf?尝试找到正确的,但我猜它失败了...
注意:仍然不完全确定谁是使用 google::
命名空间的坏人,因为 libortools 合并了它的所有静态依赖项,但我想现在你明白了这个错误......
note2: 我在 mizux/shared 分支中有一个补丁 https://github.com/google/or-tools/commit/805bc0600f4b5645114da704a0eb04a0b1058e28#diff-e8590fe6fb5044985c8bf8c9e73c0d88R114
警告:该分支当前已损坏且尚未准备就绪。对于 unix,我正在尝试从静态依赖项转变为动态依赖项,因此我需要修复所有 rpath、传递性 deps 等......并且在这个过程中我还必须解决这个问题(我在使用时没有重现静态依赖)
如果完成时间太长(我们应该在 2018 年 5 月底之前创建一个版本 6.7.2 或 6.8(即新工件)),它可能只包含此修复程序而不是我的分支...