代码之家  ›  专栏  ›  技术社区  ›  Yash Ugrankar

加载JNI依赖项时,即使在成功加载所需的对象文件后,也会在Java中获取未满足的LinkError(未定义的符号)

  •  1
  • Yash Ugrankar  · 技术社区  · 6 年前

    我正在使用Google或tools library(v6.4)进行一个项目(尽管我的问题并不是针对这个库)。这包括一个jar,它有几个本机依赖项(一组“.so”/.dylib”对象文件,具体取决于操作系统)。我的这个项目的构建是在Ubuntu 14.04上完成的

    我面临的问题是:在运行时尝试加载特定的对象文件时(使用 System.load() ),我收到一个不满意的链接错误,消息为“undefined symbol”(我在下面添加了stacktrace)。但是,我正在加载在此之前定义此符号的对象文件,所以我不确定为什么会抛出此错误。

    我以以下方式加载依赖项:对象文件被打包到Maven在构建期间创建的jar中,并被提取和加载(使用 系统加载() )在运行时。其方法如下:

    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);
        }
    }
    

    运行时 系统加载() 对于libdimacs。因此,抛出了一个UnsatifiedLinkError。堆栈跟踪:

    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)
    

    但是,libortools中存在此符号“\u ZN6google14FlagRegistererC1IbEEPKcS3\u S3\u PT\u S5\u”。所以,它是在libdimacs之前加载的。我通过运行以下命令验证了这一点:

    objdump -t (LIBRARY_PATH)/libortools.so | grep _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
    

    这给了我以下输出:

    0000000000ce12cc gw    F .text       00000091 _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
    

    因此,似乎符号应该在 系统加载() 调用,除非加载包含的对象文件时出现问题。为了检查对象文件是否已正确加载,我使用了中详细介绍的方法 this solution .除了答案中详述的课程外,我在后面添加了以下几行 系统加载() 请来 EnvironmentUtils.loadResourceFromJar() 要打印最近加载的库名称,请执行以下操作:

    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]);
        }
    }
    

    输出(直到UnsatifiedLinkError之前)如下所示:

    /tmp/libfap.so
    /tmp/libcvrptw_lib.so
    /tmp/libortools.so
    

    所以libortools。因此似乎加载正确,这意味着符号应该加载到内存中。完全相同的代码与相应的Mac(“.dylib”)依赖项(构建于MacOS Sierra 10.12.5)完美配合。如果您有任何关于解决此问题的建议,我们将不胜感激。非常感谢。

    1 回复  |  直到 6 年前
        1
  •  0
  •   Mizux The Dreams Wind    6 年前

    很抱歉,java工件目前可能已损坏。。。

    可以使用c++filt对符号进行demangle;)

    c++filt _ZN6google14FlagRegistererC1IbEEPKcS3_S3_PT_S5_
    google::FlagRegisterer::FlagRegisterer<bool>(char const*, char const*, char const*, bool*, bool*)
    

    事实上,gflag最近将其名称空间从 google:: gflags:: 还有glog还是protobobuf?试着找到正确的一个,我想它失败了。。。
    注意:仍然不能完全确定谁是使用 谷歌:: 名称空间,因为libortools合并了它的所有静态依赖项,但我想现在您已经理解了这个bug。。。

    注2:我在mizux/共享分支中有一个补丁 https://github.com/google/or-tools/commit/805bc0600f4b5645114da704a0eb04a0b1058e28#diff-e8590fe6fb5044985c8bf8c9e73c0d88R114
    警告 :此分支当前已断开,尚未准备就绪。对于unix,我正在尝试从静态依赖项转移到动态依赖项,因此我需要修复所有rpath、传递的DEP等。。。在这个过程中,我还必须解决这个问题(在使用静态依赖关系时,我没有复制这个问题)

    如果太长时间无法完成(我们应该在2018年5月底之前创建一个版本6.7.2或6.8(即新工件),其中可能只包含此修复,而不包含我的分支。。。