为什么在 JVM 下运行时,我的 Ada 共享库会出现“存储错误”

Jea*_*bre 3 java linux ada segmentation-fault gnat

我们有一个由 GnatPro 19.2 编译的 Ada 共享库,我们通过 JNA 调用来调用它。

我们的应用程序在 windows 下运行良好。在 Linux 下移植时,应用程序随机崩溃并出现 Ada 异常:

storage error or erroneous memory access.
Run Code Online (Sandbox Code Playgroud)

使用 gdb 调试(附加进程)并没有多大帮助。我们得到各种 SIGSEGV,我们继续,一段时间后我们得到存储错误,没有可用的调用堆栈。

我们的共享库可以与 python 本机调用一起使用,没有任何问题。问题可能出在 Java 方面。

尝试切换 JVM(openjdk 或官方 jdk)但没有运气。

为什么是这样?有没有办法解决它?

Jea*_*bre 7

第一个提示是在尝试将调试器附加到应用程序时获得一堆 SIGSEGV,然后在继续时看到程序正在恢复。

这意味着 SIGSEGV 信号是在 Java 端处理的,正如为什么 java 应用程序在 gdb 中崩溃但在现实生活中正常运行?.

Java 使用推测性加载。如果指针指向可寻址内存,则加载成功。很少有指针不指向可寻址内存,并且尝试加载会生成 SIGSEGV ... java 运行时拦截,使内存再次可寻址,并重新启动加载指令。

现在发生的情况是,默认情况下,GNAT 运行时会安装一个新的信号处理程序来捕获 SIGSEGV 并重定向到一个干净的 Ada 异常。Ada 异常的一个有趣特性是,即使没有调试器,它们也可以打印堆栈跟踪。此 SIGSEGV 处理程序重定向允许这样做。

但是就Java而言,由于Java使用了推测性加载,因此Java端会时不时地出现SIGSEGV。所以当 Ada 共享库被加载和初始化时,Ada SIGSEGV 处理程序被安装,并捕获那些“正常”的 SIGSEGV,并立即中止。

请注意,它不会在 Windows 下发生。由于处理内存冲突访问时的 Windows 限制,java 运行时可能无法使用此推测加载机制。

信号处理在 s-intman.adb

 --  Check that treatment of exception propagation here is consistent with
  --  treatment of the abort signal in System.Task_Primitives.Operations.

  case signo is
     when SIGFPE  => raise Constraint_Error;
     when SIGILL  => raise Program_Error;
  --   when SIGSEGV => raise Storage_Error;  -- commenting this line should fix it
     when SIGBUS  => raise Storage_Error;
     when others  => null;
  end case;
end Notify_Exception;
Run Code Online (Sandbox Code Playgroud)

现在我们必须重建一个新的本机运行时并使用它而不是默认的运行时。这非常乏味且容易出错。该文件是 gnarl 库的一部分。我们必须使用适当的选项动态重建-gnatp -nostdinc -O2 -fPICgnarl 库以创建 gnatrl 库替换……并在升级编译器时再次执行此操作……

幸运的是,AdaCore 提供了一个替代解决方案:

首先在.gpr项目目录中创建一个 pragmas 文件(我们称之为no_sigsegv.adc),其中包含:

pragma Interrupt_State (SIGSEGV, SYSTEM); 
Run Code Online (Sandbox Code Playgroud)

指示运行时不要安装 SIGSEGV 处理程序

然后将此添加到文件的Compiler包中.gpr:

  package Compiler is
    ...
      for local_configuration_pragmas use Project'Project_dir & "/no_sigsegv.adc";
Run Code Online (Sandbox Code Playgroud)

并从头开始重建一切。测试:没有任何崩溃。