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源創計劃”,歡迎正在閱讀的你也加入,一起分享。