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

2019年9月17日 12点热度 0条评论

我们有一个由GnatPro 19.2编译的Ada共享库,我们通过JNA调用对其进行调用。
我们的应用程序在Windows下运行良好。在Linux下移植它时,应用程序会随机崩溃,并出现Ada异常:

storage error or erroneous memory access.

使用gdb进行调试(附加过程)并没有太大帮助。我们得到各种SIGSEGV,继续,然后过一会儿,我们得到了存储错误,没有可用的调用栈。

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

尝试切换JVM(openjdk或官方jdk)时不走运。

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

解决方案如下:

第一个提示是,在尝试将调试器附加到应用程序时收到一堆SIGSEGV,然后在继续运行时看到程序恢复运行。
这意味着SIGSEGV信号是在Java端处理的,如Why does java app crash in gdb but runs normally in real life?中所确认。

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

现在发生的是,默认情况下,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;

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

幸运的是,AdaCore提供了另一种解决方案:

首先在
.gpr项目目录中创建一个编译指示文件(我们将其称为
no_sigsegv.adc),该文件包含:

pragma Interrupt_State (SIGSEGV, SYSTEM); 

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

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

  package Compiler is
    ...
      for local_configuration_pragmas use Project'Project_dir & "/no_sigsegv.adc";

并从头开始重建所有内容。测试:绝不会发生任何一次崩溃。