不满意 Link 错误 - 库加载,找不到条目

Unsatisfied Link Error - library loads, entry not found

我正在尝试 link 一个由客户编写的(相当大的)本机库 java 代码。我已经编写了这个尝试加载库的简化测试 class 并从库中调用本地方法。我还添加了一些调试代码。

public class JniVtaTest {

  static {
    try {
      Process exec = Runtime.getRuntime().exec("ldd /usr/java/packages/lib/libvtajni.so");
      byte[] bytes = exec.getErrorStream().readAllBytes();
      System.out.println(new String(bytes));
      bytes = exec.getInputStream().readAllBytes();
      System.out.println(new String(bytes));
    } catch (IOException e) {
      e.printStackTrace();
    }
    String property = System.getProperty("java.library.path");
    System.out.println(property);

    // above code generates the debugging outpout shown below
    System.loadLibrary("vtajni");
  }

  // running with options:
  // -Djava.library.path="/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu" -verbose:jni -Xcheck:jni

  public static void main(String[] args) {
    new JniVtaTest().vtaAnalyze(args[0]);
  }

  private native String vtaAnalyze(String str);
}

当我 运行 使用上面提到的选项时,我得到了很多关于动态的 jvm 输出 linking of jvm classes 然后这个:

    linux-vdso.so.1 (0x00007ffe2239d000)
    libiodbc.so.2 => /usr/lib/x86_64-linux-gnu/libiodbc.so.2 (0x00007ff6093b1000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007ff609028000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff608c8a000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007ff608a72000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff608681000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff60847d000)
    /lib64/ld-linux-x86-64.so.2 (0x00007ff62713d000)

11.0.5
/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib:/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu
Exception in thread "main" java.lang.UnsatisfiedLinkError: 'java.lang.String com.customer.jni.JniVtaTest.vtaAnalyze(java.lang.String)'
    at com.customer.jni.JniVtaTest.vtaAnalyze(Native Method)
    at com.customer.jni.JniVtaTest.main(JniVtaTest.java:26)

库要加载的所有库似乎都存在:

gus@ns-l1:/usr/java/packages$ ls -al /usr/lib/x86_64-linux-gnu/libiodbc.so.2
lrwxrwxrwx 1 root root 18 Dec 12  2017 /usr/lib/x86_64-linux-gnu/libiodbc.so.2 -> libiodbc.so.2.1.20
gus@ns-l1:/usr/java/packages$ ls -al /usr/lib/x86_64-linux-gnu/libstdc++.so.6
lrwxrwxrwx 1 root root 19 Dec  4 09:45 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 -> libstdc++.so.6.0.25
gus@ns-l1:/usr/java/packages$ ls -al /lib/x86_64-linux-gnu/libm.so.6
lrwxrwxrwx 1 root root 12 Apr 16  2018 /lib/x86_64-linux-gnu/libm.so.6 -> libm-2.27.so
gus@ns-l1:/usr/java/packages$ ls -al /lib/x86_64-linux-gnu/libgcc_s.so.1
-rw-r--r-- 1 root root 96616 Dec  4 09:45 /lib/x86_64-linux-gnu/libgcc_s.so.1
gus@ns-l1:/usr/java/packages$ ls -al /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Apr 16  2018 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.27.so
gus@ns-l1:/usr/java/packages$ ls -al /lib/x86_64-linux-gnu/libdl.so.2
lrwxrwxrwx 1 root root 13 Apr 16  2018 /lib/x86_64-linux-gnu/libdl.so.2 -> libdl-2.27.so
gus@ns-l1:/usr/java/packages$ ls -al /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx 1 root root 32 Apr 16  2018 /lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.27.so
gus@ns-l1:/usr/java/packages$ 

它们也都可以通过 ldconfig -v -N 找到,我检查过 ldconfig 没有找到任何 带有字母 vta 的库是这样的,所以我认为我可以避免意外的名称重叠:

ldconfig -v -N 2>&1 | grep vta
(no output shown)

客户库本身显然已加载,因为当我调试时它不会在加载时死机 库,调试器将停止在调用该方法的行上,并逐步进入 此 JVM 代码显示它找到了一个名为 /usr/java/packages/lib/libvtajni.so 的库 并且它首先进入 for 循环寻找 Java_com_customer_jni_JniVtaTest_vtaAnalyze 然后再次寻找 Java_com_customer_jni_JniVtaTest_vtaAnalyze__Ljava_lang_String_2 (似乎每个检查两次)

    private static long findNative(ClassLoader loader, String entryName) {
        Map<String, NativeLibrary> libs =
            loader != null ? loader.nativeLibraries() : systemNativeLibraries();
        if (libs.isEmpty())
            return 0;

        // the native libraries map may be updated in another thread
        // when a native library is being loaded.  No symbol will be
        // searched from it yet.
        for (NativeLibrary lib : libs.values()) {
            long entry = lib.findEntry(entryName); <<<<< STOP DEBUGGER HERE
            if (entry != 0) return entry;
        }
        return 0;
    }

javah com.customer.jni.JniVtaTest 生成的 header 文件如下所示:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_customer_jni_JniVtaTest */

#ifndef _Included_com_customer_jni_JniVtaTest
#define _Included_com_customer_jni_JniVtaTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_customer_jni_JniVtaTest
 * Method:    vtaAnalyze
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_customer_jni_JniVtaTest_vtaAnalyze
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

cpp 文件中的方法如下所示:

JNIEXPORT jstring JNICALL Java_com_customer_jni_JniVtaTest_vtaAnalyze
        (JNIEnv * env, jobject obj, jstring jInputText) {
    cout << "foo";

    // customer code...

    return  env->NewStringUTF(obuf);
}

而且我从来没有看到 foo 打印出来,所以我不相信它正在找到方法 然后在方法内部失败。

完整 JDK 和系统 (ubuntu 18.04) 信息:

openjdk 11.0.5 2019-10-15 LTS
OpenJDK Runtime Environment Zulu11.35+15-CA (build 11.0.5+10-LTS)
OpenJDK 64-Bit Server VM Zulu11.35+15-CA (build 11.0.5+10-LTS, mixed mode)

Linux ns-l1 4.15.0-88-generic #88-Ubuntu SMP Tue Feb 11 20:11:34 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

搜索和阅读 https://developer.android.com/training/articles/perf-jni#faq:-why-do-i-get-unsatisfiedlinkerror- and the spec at https://docs.oracle.com/en/java/javase/11/docs/specs/jni/design.html#resolving-native-method-names 等页面的时间让我摸不着头脑。

我的问题:为什么会出现此错误?我错过了什么。

编辑:根据评论中的问题,我没有找到 nm -D 的符号,但我确实找到了 nm -A 的东西......现在我需要弄清楚为什么会这样。

gus@ns-l1:~/clients/customer/code/vta_jni$ nm -A /usr/java/packages/lib/libvtajni.so | grep com_
/usr/java/packages/lib/libvtajni.so:00000000112a3d38 t _GLOBAL__sub_I__Z44Java_com_customer_jni_JniVtaTest_vtaAnalyzeP7JNIEnv_P8_jobjectP8_jstring
/usr/java/packages/lib/libvtajni.so:00000000112a388a T _Z44Java_com_customer_jni_JniVtaTest_vtaAnalyzeP7JNIEnv_P8_jobjectP8_jstring

编辑 2:header 文件包含在...

#include "com_customer_jni_JniVtaTest.h"

编辑 3:将 cmake 文件更改为使用 add_library(vtajni SHARED ...(并使用 -fPIC 重新编译客户端代码)后,我现在得到:

gus@ns-l1:~/clients/customer/code/vta_jni$ nm -D /usr/java/packages/lib/libvtajni.so | grep com_
00000000113587ba T _Z44Java_com_customer_jni_JniVtaTest_vtaAnalyzeP7JNIEnv_P8_jobjectP8_jstring

但是名称仍然被破坏,这表明 extern 存在第二个问题,如下面的评论所述,但 header 已包含在上面。

已解决: 修改是由于忘记了调试编辑,注释掉了 C 项目中 header 文件的外部部分(但原始生成的文件在我上面粘贴的 java 项目中仍然有它)我现在 "successfully" 导致它打印

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

要使函数正常工作,nm -D yourfile.so 中的确切名称必须以大写字母 T 显示。如果没有,你必须找出原因。

这是一个包含三个函数的示例文件,说明了常见问题:

extern "C" {
  void correct();
  extern void notInThisSo();
}
void correct() { }

void missingJniHeader() {}

static void* dummyUsage = (void*) &notInThisSo;

这是 nm 输出(去除前导零):

$ gcc foo.cc -shared -o foo.so && nm -D foo.so
000010f5 T correct                     # This works
         w __cxa_finalize
         w __gmon_start__
         w _ITM_deregisterTMCloneTable
         w _ITM_registerTMCloneTable
         U notInThisSo                 # This name is believed to be in another .so
000010fc T _Z16missingJniHeaderv       # This name is C++ mangled: missing extern "C" from header

如果 nm -D 中没有显示您的名字,请检查 nm -A yourfile.so:

$ cat bar.cc
extern "C" {
  __attribute((visibility("default"))) void visible() {}
  void not_visible() { }
}

$ gcc  -fvisibility=hidden bar.cc -shared -o bar.so  && nm -A bar.so
[...]
bar.so:000010fc t not_visible
bar.so:000010f5 T visible

在这里你可以看到 not_visible 有一个小写字母 t,因为构建使用 -fvisibility=hidden 来隐藏符号,并且没有明确地将其列入白名单。 JNI 无法访问隐藏的符号。

(如果nm -A给出nm: bar.so: no symbols,这意味着库被剥离。您仍然可以在剥离的库上使用nm -D