JNI 中錯誤的信號處理導致 JVM 崩潰問題分析

語言: CN / TW / HK

編者按:JNI 是 Java 和 C 語言交互的主要手段,要想做好 JNI 的編程並不容易,需要了解 JVM 內部機理才能避免一些錯誤。本文分析 Cassandra 使用 JNI 本地庫導致 JVM 崩潰的一個案例,最後定位問題根源是信號的錯誤處理(一些 C 編程人員經常會截獲信號,做一些額外的處理),該案例提示 JNI 編程時不要隨意截獲信號處理。

現象

在使用 Cassandra 時遇到運行時多個位置都有發生 crash 現象,並且沒有 hs_err 文件生成,這裏列舉了其中一個 crash 位置:

分析

首先直接基於上面這個 crash 的 core 文件展開分析,下面分別是對應源碼上下文和指令上下文:

使用 GDB 調試對應的 core 文件,如下圖所示:

在 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 信號處理函數中,代碼如下:

由於 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 入羣。


本文分享自微信公眾號 - openEuler(openEulercommunity)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閲讀的你也加入,一起分享。