如何在JNI中用sigaction()捕获三类信号且不干扰JVM
我的目标是捕获原生代码产生的三类信号:SIGSEGV(由无限递归触发)、SIGALRM(由计时器触发,用于检测无限循环)和SIGFPE(除零错误),防止Minecraft(Java版)在运行我开发的编译型模组语言编写的模组时,因这类问题崩溃或挂起。
我已经在C/C++/Python程序中通过sigaction()和sigsetjmp()实现了该功能,但在Java环境中遇到了障碍,正如Stack Overflow上的一篇回答所述:
Java虚拟机(至少Oracle及OpenJDK实现)使用POSIX信号进行内部通信,因此会为SIGSEGV等信号安装处理程序。这意味着JVM必须获取并检查进程中发生的所有SIGSEGV信号,以区分用于内部通信的信号和真正的程序错误信号。
但信号处理程序是全局资源,进程内的任何原生代码都可以安装信号处理程序,替换JVM已安装的处理程序。
为解决用户安装的信号处理程序替换JVM处理程序的问题,同时兼容有安装信号处理程序需求的用户代码,JVM采用了‘信号链’机制:JVM的信号处理程序先运行,若判定信号与VM无关,则将其传递给用户处理程序。
libjsig.so是解决该问题的方案:它替换系统的信号API(如sigaction()),用户代码安装的信号处理程序不会替换全局处理程序,而是被链在JVM已安装的处理程序之后。
需要注意的是,从信号处理程序中调用siglongjmp()极易引发未定义行为,因此我的模组语言会在关键节点通过sigprocmask阻塞并禁用信号处理程序。
按照建议使用jsig时,只需在java命令前添加LD_PRELOAD=<你的JDK路径>/libjsig.so,但这会导致JVM优先接收SIGSEGV信号,而JVM认为该信号不可恢复,不会将其传递给我的自定义处理程序,最终导致程序崩溃。
若不使用jsig,JVM会定期检查信号处理程序是否被篡改并发出警告——因为我的C代码覆盖了JVM自身的SIGSEGV处理程序(JVM用它来优化空检查等内部操作),这会导致游戏偶尔崩溃。
如何在不干扰JVM的前提下,妥善恢复我的C代码产生的这些无害信号?
内容的提问来源于stack exchange,提问作者MyNameIsTrez

