JNA 回调在对本机库的特定内部方法调用时间歇性地使应用程序崩溃

JNA Callback crashing the application intermittently on specific internal method call to native library

在我的应用程序中,我使用 JNA 来使用用 C 编写的本机代码。我在回调中从本机应用程序获取通知。

在回调中,我得到一个指针和一些其他数据来处理。在 JNA 回调代码中,我必须再次使用此指针来调用其他一些本机库代码并且必须传递该指针。之后我必须从这个回调中 return。

如果我不从传递指针的回调中调用那个中间本机库方法,它工作正常,但如果我添加这个调用,我的应用程序会间歇性地崩溃(主要是在处理几百个回调请求之后,有时它运行 数千次回调也成功)。

在本机代码中为其设置挂钩的NotificationHook class 个对象是一个静态变量,因为应用程序只有一个挂钩。本机库一一调用。

public interface INotificationHook extends Callback {

  public int NotificationHook(TRANX htrans, NOTIFICATION.ByReference notification);

}


public class NotificationHook implements INotificationHook {

    @Override
    public int NotificationHook(final TRANX tranx, final NOTIFICATION.ByReference notification) {
       System.out.println("Enter Java Callback");
       // notification contains actual data to process
       library.SendSrvcResponse(tranx); // if I put this method call, application crashes intermittently

       System.out.println("Exit Java Callback");
       return 0;
    }

}

TRANX 原生结构:

typedef struct tagTRANX { int unused; } *TRANX;

TRANX.java

import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;

@FieldOrder({"unused"})
public class TRANX extends Structure {

  public static class ByReference extends TRANX implements Structure.ByReference {
  }

  public static class ByValue extends TRANX implements Structure.ByValue {
  }

  public int unused = 0;

  public TRANX () {
    super();
  }

  public TRANX(final int unused) {
    super();
    this.unused = unused;
  }

  public TRANX(final Pointer peer) {
    super(peer);
  }
}

JNA 中的库定义 (java):

int SendSrvcResponse(TRANX tranx);

这个指针实际上是一个结构指针,我试图创建一个结构并用它替换这个指针,即使在这种情况下应用程序崩溃了。

当我添加了一些入口出口日志时,观察如下:

printf("Enter to JNA from c library for callback");
hs = (*piHook->hookProc) (
tranx, notification); 
printf("Callback completed from JNA and returned to c library");

中间调用c库:

SendSrvcResponse(TRANX tranx) {
    printf("Enter to send response in c library");
    // do some operation
    printf("Enter to send response in c library");
}

每次执行成功时,这是我从控制台(集体 JNA 和 C 库)中的打印日志中得到的:

  1. 从c库进入JNA // C库调用JNA回调
  2. Enter Java回调 // Java回调被调用回车发送
  3. response in c library //调用native库中的方法发送
  4. response enter to send response in c library // native library中的方法发送response后退出
  5. 退出Java回调 //Java 回调完成它的操作
  6. Callback completed from JNA and returned to c library // JNA回调完成后,returning the flow back to c library 代码

在应用程序崩溃的情况下,打印日志中缺少最后一条语句。只有 JNA return 方法流回 C,但未被 C 接收。

通知是指针。

崩溃日志:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x0000ffff6c78f610, pid=6396, tid=0x0000ffff6d9f0ac8
#
# JRE version: OpenJDK Runtime Environment (8.0_272-b10) (build 1.8.0_272-b10)
# Java VM: OpenJDK 64-Bit Server VM (25.272-b10 mixed mode linux-aarch64 compressed oops)
# Derivative: IcedTea 3.17.0
# Distribution: Custom build (Fri Dec 11 01:04:16 UTC 2020)
# Problematic frame:
# C  [jna791751318727750086.tmp+0xa610]  Java_com_sun_jna_Native_setByte+0x50
#
# Core dump written. Default location: **************
#
# If you would like to submit a bug report, please include
# instructions on how to reproduce the bug and visit:
#   https://icedtea.classpath.org/bugzilla
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

---------------  T H R E A D  ---------------

Current thread (0x0000ffff6c7b7000):  JavaThread "Thread-5356" [_thread_in_native, id=6642, stack(0x0000ffff6d9d0000,0x0000ffff6d9f0ac8)]

siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000ffff6c2ec018

Registers:
R0=0x0000000000000000
R1=0x0000000000000000
R2=0x0000ffff6c2ec018
R3=0x0000000000000000
R4=0x0000000000000000
R5=0x0000000000000000
R6=0x0000ffff66b49458
R7=0x0000ffff66b49458
R8=0x0000ffff6c78f5c0
R9=0x0000ffff6c7b72c8
R10=0x0000000020002230
R11=0x00000000f212dde8
R12=0x0000ffff691637f0
R13=0x0000000020002230
R14=0x00000000f212e4e0
R15=0x00000000f213acd0
R16=0x00000001006c7770
R17=0x0000000000000000
R18=0x0000000000000001
R19=0x0000ffff6c7aa250
R20=0x0000000000000000
R21=0x00000000f212e2b0
R22=0x0000000100781ed8
R23=0x00000000f212fd60
R24=0x00000000f213a868
R25=0x00000001000016d0
R26=0x00000000f213a880
R27=0x0000000000000000
R28=0x0000ffff6c7b7000
R29=0x0000ffff6d9efcd0
R30=0x0000ffff78fea394

Top of Stack: (sp=0x0000ffff6d9efcd0)
0x0000ffff6d9efcd0:   0000ffff6d9efd60 0000ffff78fea394
0x0000ffff6d9efce0:   00000000200f03db 0000000000000000
0x0000ffff6d9efcf0:   0000ffff6c2ec018 0000000000000000
0x0000ffff6d9efd00:   0000ffff6c7b7250 00aebfc8f212dfd8
0x0000ffff6d9efd10:   00000000f212dde8 00000000f21343b0
0x0000ffff6d9efd20:   00000000f21342f0 00000000f2134350
0x0000ffff6d9efd30:   00000000f2134368 00000000f2134380
0x0000ffff6d9efd40:   00000000f2134320 00000000f2134398
0x0000ffff6d9efd50:   00000000fca7ebc8 0000ffff77adb000
0x0000ffff6d9efd60:   0000ffff6d9f0220 0000ffff7988acb4
0x0000ffff6d9efd70:   00000000f21343c8 00000000f213a978
0x0000ffff6d9efd80:   0000ffff6d9f0220 0000ffff79952fbc
0x0000ffff6d9efd90:   00000000f213aa28 00000000f213aa68
0x0000ffff6d9efda0:   0000000000000000 00000000f213aae8
0x0000ffff6d9efdb0:   0000ffff6d9efdd0 0000ffff80d78678
0x0000ffff6d9efdc0:   0000000000000000 00000001000115f0
0x0000ffff6d9efdd0:   00000000fc391290 00000000f212e4e0
0x0000ffff6d9efde0:   00000000f212dfd8 00000000fc3a3960
0x0000ffff6d9efdf0:   00000000200eada6 0000000000000000
0x0000ffff6d9efe00:   00000000f21342e0 0000ffff6c7b7000
0x0000ffff6d9efe10:   00000000fc43c670 00000000d024ce28
0x0000ffff6d9efe20:   0000ffff6d9f0220 0000ffff799275fc
0x0000ffff6d9efe30:   00000000f212e4e0 00000000f212fb00
0x0000ffff6d9efe40:   00000000d0d14760 00000000d0d14760
0x0000ffff6d9efe50:   00000000f2131bc0 f212f75000000000
0x0000ffff6d9efe60:   0000ffff6d9f0220 0000ffff78eccdd8
0x0000ffff6d9efe70:   00000000f2131c20 00000000f212e398
0x0000ffff6d9efe80:   0000ffff6d9f0220 0000ffff79463d2c
0x0000ffff6d9efe90:   200f03db00000000 0000000100781ed8
0x0000ffff6d9efea0:   00000000f213acb0 00000000f21342e0
0x0000ffff6d9efeb0:   00000000f212e398 0000000000000000
0x0000ffff6d9efec0:   00000000f212e4e0 0000000000000000 

Instructions: (pc=0x0000ffff6c78f610)
0x0000ffff6c78f5f0:   e1 ff 00 91 1f 00 01 eb a3 02 00 54 e2 0f 42 a9
0x0000ffff6c78f600:   c0 00 00 f0 00 40 09 91 e1 ff 40 39 00 20 42 b9
0x0000ffff6c78f610:   41 68 23 38 e0 00 00 34 e0 1b 40 f9 22 00 00 b0
0x0000ffff6c78f620:   21 00 00 b0 42 80 30 91 21 e0 30 91 45 e5 ff 97 

Register to memory mapping:

R0=0x0000000000000000
R1=0x0000000000000000
R2=0x0000ffff6c2ec018
R3=0x0000000000000000
R4=0x0000000000000000
R5=0x0000000000000000
R6=0x0000ffff66b49458
R7=0x0000ffff66b49458
R8=0x0000ffff6c78f5c0
R9=0x0000ffff6c7b72c8
R10=0x0000000020002230
R11=0x00000000f212dde8
R12=0x0000ffff691637f0
R13=0x0000000020002230
R14=0x00000000f212e4e0
R15=0x00000000f213acd0
R16=0x00000001006c7770
R17=0x0000000000000000
R18=0x0000000000000001
R19=0x0000ffff6c7aa250
R20=0x0000000000000000
R21=0x00000000f212e2b0
R22=0x0000000100781ed8
R23=0x00000000f212fd60
R24=0x00000000f213a868
R25=0x00000001000016d0
R26=0x00000000f213a880
R27=0x0000000000000000
R28=0x0000ffff6c7b7000
R29=0x0000ffff6d9efcd0
R30=0x0000ffff78fea394


Stack: [0x0000ffff6d9d0000,0x0000ffff6d9f0ac8],  sp=0x0000ffff6d9efcd0,  free space=127k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [jna791751318727750086.tmp+0xa610]  Java_com_sun_jna_Native_setByte+0x50
J 5174  com.sun.jna.Native.setByte(Lcom/sun/jna/Pointer;JJB)V (0 bytes) @ 0x0000ffff78fea394 [0x0000ffff78fea300+0x94]
V  [libjvm.so+0x40f678]
C  0x00000000f212e4e0

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
J 5174  com.sun.jna.Native.setByte(Lcom/sun/jna/Pointer;JJB)V (0 bytes) @ 0x0000ffff78fea398 [0x0000ffff78fea300+0x98]
J 9695 C2 com.sun.jna.Pointer.setByte(JB)V (11 bytes) @ 0x0000ffff7988acb4 [0x0000ffff7988ac80+0x34]
J 9266 C2 com.sun.jna.Pointer.setValue(JLjava/lang/Object;Ljava/lang/Class;)V (607 bytes) @ 0x0000ffff79952fbc [0x0000ffff79951ec0+0x10fc]
J 9230 C2 com.sun.jna.Structure.writeField(Lcom/sun/jna/Structure$StructField;)V (427 bytes) @ 0x0000ffff799275fc [0x0000ffff79927500+0xfc]
J 9423 C2 com.sun.jna.Structure.write()V (126 bytes) @ 0x0000ffff79463d2c [0x0000ffff79463840+0x4ec]
J 9434 C2 com.sun.jna.Structure.autoWrite()V (45 bytes) @ 0x0000ffff7904e274 [0x0000ffff7904e240+0x34]
J 9905 C1 com.sun.jna.CallbackReference$DefaultCallbackProxy.invokeCallback([Ljava/lang/Object;)Ljava/lang/Object; (238 bytes) @ 0x0000ffff78ee17c4 [0x0000ffff78ede580+0x3244]
J 9904 C1 com.sun.jna.CallbackReference$DefaultCallbackProxy.callback([Ljava/lang/Object;)Ljava/lang/Object; (22 bytes) @ 0x0000ffff78fa18b0 [0x0000ffff78fa1800+0xb0]
v  ~StubRoutines::call_stub

我尝试过的事情:

  1. 用 JNA 回调输入中的指针替换 TRANX。
  2. 在 Java 回调方法的最后一行的结构上调用 clear()
  3. 在JNA中注释完整的处理回调代码,只保留对SendSrvcResponse方法和return回调的调用。

以上都导致崩溃,none 很有帮助。

一个奇怪的观察是,当在本机 c 代码中实现此回调时,应用程序不会中断。它只有在与 JNA 集成时才会中断。

谁能帮我了解一下这可能是什么原因,或者我该如何进一步调查?

根据您提供的代码,问题与此处的其他 Callback-related 问题相同:由于 Java,您正在丢失 TRANX 的本机分配'垃圾 collection.

JNA Structure 由两部分组成:指针(指向数据)和数据本身。您没有为 TRANX 提供本机 typedef 来确认您的 JNA 映射,但是实例化的 object 将有一个内部指针引用,指向 4 字节的内存分配(int unused ).

您只显示 TRANX 已经是参数的回调代码,这意味着您已经实例化它以传递给回调。

如果你使用new TRANX()new TRANX(int unused)自己分配,那么JNA有

  • 分配了 4 个字节的本机内存
  • 在内部存储指向它的指针

在 JNA 中,附加到 Structure 的本机内存作为垃圾 collection 进程的一部分自动释放。这是回调的一个常见问题,因为您通常不会控制回调的时间return,所以会出现以下顺序:

  • 您在 Java 中创建 object(分配 TRANX 结构在内部跟踪指针的本机 4 个字节)
  • 您将 TRANX object 传递给回调
  • 通过 object 后,Java 不再需要它自己的 object;它无法访问,因此符合垃圾 collection
  • 当 GC 发生时,本机 4 个字节作为进程的一部分被释放
  • 回调中的 TRANX object 内部仍然有指针,但它现在指向不再分配的内存,导致 SIGSEGV (或无效内存访问如果内存由另一个线程分配或其他未定义的行为,则会出现错误或奇怪的症状。

问题的解决方法是跟踪与TRANX相关的内存。

  • 如果您自己分配它,请保留对 TRANX object 的引用以防止它无法访问。
    • 这通常需要在您确定回调已被处理后的某个时间点访问 TRANX 结构
    • 在 JDK9+ 中,可以使用 ReachabilityFence
    • 在 JDK8 中,您应该以某种方式操作 class(例如,从中读取一个值,或对其调用 toString 等)。
  • 如果您正在使用本机分配并从本机 API 的 peer 值 return 创建指针,然后阅读 API 以确定何时该内存已释放。