JNI 中错误的信号处理导致 JVM 崩溃问题分析

编者按:JNI 是 Java 和 C 语言交互的主要手段,要想做好 JNI 的编程并不容易,需要了解 JVM 内部机理才能避免一些错误。本文分析 Cassandra 使用 JNI 本地库导致 JVM 崩溃的一个案例,最后定位问题根源是信号的错误处理(一些 C 编程人员经常会截获信号,做一些额外的处理),该案例提示 JNI 编程时不要随意截获信号处理。

现象

在使用 Cassandra 时遇到运行时多个位置都有发生 crash 现象,并且没有 hs_err 文件生成,这里列举了其中一个 crash 位置:

JNI 中错误的信号处理导致 JVM 崩溃问题分析

分析

首先直接基于上面这个 crash 的 core 文件展开分析,下面分别是对应源码上下文和指令上下文:

JNI 中错误的信号处理导致 JVM 崩溃问题分析

使用 GDB 调试对应的 core 文件,如下图所示:

JNI 中错误的信号处理导致 JVM 崩溃问题分析

在 GDB 中进行单步调试(GDB 调试可以参考官方文档),配合源代码发现 crash 的原因是传入的 name 为 null,导致调用 name.split("/_") 时触发了 SIGSEGV 信号,直接 crash。暂时抛开这个方法传入 namenull 是否有问题不论,从 JVM 运行的机制来说,这里有个疑问,遇到一个 Null Pointer 为什么不是抛出 Null Pointer Exception(简称 NPE)而是直接 crash 了呢?

这里有一个知识需要普及一下:Java 层面的 NPE 主要分为两类,一类是代码中主动抛出 NPE 异常,并被 JVM 捕获 (这里的代码既可以是 Java 代码,也可以是 JVM 内部代码);另一类隐式 NPE(其原理是 JVM 内部遇到空指针访问,会产生 SIGSEGV 信号, 在 JVM 内部还会检查运行时是否存在 SIGSEGV 信号)。

带着上面的疑问,又看了几处其他位置的 crash,发现都是因为对象为 null 导致的 SIGSEGV,却都没有抛出 NPE,而是直接 crash 了,再结合都没有 hs_err 文件生成的现象, hs_err 文件生成功能位于 JVM 的 SIGSEGV 信号处理函数中,代码如下:

JNI 中错误的信号处理导致 JVM 崩溃问题分析

由于 hs_err 文件没有产生,一个很自然的推断:Cassandra 运行中可能篡改了或者捕获了 SIGSEGV 信号,并且可能做了处理,以至于 JVM 无法正常处理 SIGSEGV 信号。

然后排查业务方是否在 Cassandra 中用到了自定义的第三方 native 库,果然笔者所猜测的,有两个 native 库里都对 SIGSEGV 信号做了捕获,注释掉这些代码后重新跑对方的业务,crash 现象不再发生,问题(由于 Cassandra 中对 NPE 有异常处理导致 JVM 崩溃)解决。

总结

C/C++ 的组件在配合 Java 一起使用时,需要注意的一点就是不要随意去捕获系统信号,特别是 SIGSEGV、SIGILL、SIGBUS 等,因为会覆盖掉 JVM 中的信号捕获逻辑。附录 这里贴一个 demo 可以用来复现 SIGSEGV 信号覆盖造成的后果,有兴趣的可以跑一下:

// JNITest.java
import java.util.UUID;
public class JNITest {
public static void main(String[] args) throws Exception {
System.loadLibrary("JNITest");
UUID.fromString(null);
}
}
// JNITest.c
#include <signal.h>
#include <jni.h>

JNIEXPORT
jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) 
{
signal(SIGSEGV, SIG_DFL);//如果注释这条语句,在运行时会出现 NullPointerExcetpion 异常
return JNI_VERSION_1_8;
}

通过 GCC 编译并执行就可以触发相同的问题,编译执行命令如下:

$ gcc -Wall -shared -fPIC JNITest.c -o libJNITest.so -I$JAVA_HOME/include -I$JAVA_HOME/include/linux
$ javac JNITest.java
/$ java -Xcomp -Djava.library.path=./ JNITest

后记

如果遇到相关技术问题(包括不限于毕昇 JDK),可以进入毕昇 JDK 社区查找相关资源(点击原文进入官网),包括二进制下载、代码仓库、使用教学、安装、学习资料等。毕昇 JDK 社区每双周周二举行技术例会,同时有一个技术交流群讨论 GCC、LLVM、JDK 和 V8 等相关编译技术,感兴趣的同学可以添加如下微信小助手,回复 Compiler 入群。

JNI 中错误的信号处理导致 JVM 崩溃问题分析

本文分享自微信公众号 – openEuler(openEulercommunity)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

{{o.name}}


{{m.name}}

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/71684.html

(0)
上一篇 2021年8月11日 20:14
下一篇 2021年8月11日 20:14

相关推荐

发表回复

登录后才能评论