JVMTI 中的JNI系列函式,執行緒安全及除錯技巧
JVMTI 中的JNI系列函式,執行緒安全及除錯技巧
jni functions
在使用 JVMTI 的過程中,有一大系列的函式是在 JVMTI 的文件中
沒有提及的,但在實際使用卻是非常有用的。這就是 jni functions.
例如,在使用 SingleStep 函式時,
void JNICALL
SingleStep(jvmtiEnv *jvmti_env,
JNIEnv* jni_env,
jthread thread,
jmethodID method ,
jlocation location)
- 1
- 2
- 3
- 4
- 5
- 6
我們就可以使用 jni_env 來呼叫 jni functions 中的一系列函式。例如:
jclass mainClass = (*jni_env)->FindClass(jni_env, "Foo");
jfieldID fieldID = (*jni_env)->GetStaticFieldID(jni_env, mainClass, "x", "I");
jint fieldValue = (*jni_env)->GetStaticIntField(jni_env, mainClass, fieldID);
- 1
- 2
- 3
通過使用 jni functions 就可以獲取 Foo 類中的靜態整型變數的值。
另外在 jni functions 文件 中還有大量易用的
函式,都是在 JVMTI 文件中沒有提及的,非常有用。
事件回撥函式的執行緒安全
在 JVMTI 中有一系列回撥函式,當使用 SetEventNotificationMode
和 SetEventCallbacks
設定了某個事件的回撥函式後,Java 虛擬機器
在相應事件發生時,就會呼叫我們在 JVMTI 的 agent 中寫的回撥函式。
千萬注意:多數回撥函式需要保證執行緒安全
。
也就是說,我們寫的回撥函式要保證是可重入的,不然的話,一個執行緒執行時進入了回撥函式,還沒執行完這個回撥函式時,可能會被切換
到另一執行緒,而另一執行緒也可能重新進入這個回撥函式,執行完之後,又切換回前一執行緒時,才繼續執行上一個沒有執行完的回撥函式。如果
你使用了全域性變數,就要格外注意上述情況可能導致的資料不一致問題。
解決這個問題的最簡單的辦法就是使用 Raw Monitor。
在 Agent_OnLoad 函式中建立一個 Raw Monitor:
(*jvmti)->CreateRawMonitor(jvmti, "singelStep", &singleStepMonitorId);
- 1
然後,在回撥函式的開始和結束時,使用這個 Raw Monitor 形成一個臨界區,使得多個執行緒不能同時進入這個臨界區:
void JNICALL
callbackSingleStep(
jvmtiEnv *jvmti,
JNIEnv* jni,
jthread thread,
jmethodID method,
jlocation location) {
(*jvmti)->RawMonitorEnter(jvmti, singleStepMonitorId);
...
(*jvmti)->RawMonitorExit(jvmti, singleStepMonitorId);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
JVMTI agent 的除錯
JVMTI 的 agent 如果出錯,Java 虛擬機器就會直接崩潰,同時生成一個 log,我們需要根據這個 log 猜測 agent 中哪裡出現了錯誤,例如:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006f99f004, pid=6760, tid=8140
#
# JRE version: Java(TM) SE Runtime Environment (8.0_31-b13) (build 1.8.0_31-b13)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.31-b07 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# V [jvm.dll+0x12f004]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
#
...
Register to memory mapping:
RAX=0x0000000000000000 is an unknown value
RBX=0x000000005af15000 is a thread
RCX=0x000000005af15000 is a thread
RDX=0x00000000d707b160 is an oop
java.lang.NoSuchFieldError
- klass: 'java/lang/NoSuchFieldError'
RSP=0x000000005bcce510 is pointing into the stack for thread: 0x000000005af15000
RBP=0x000000005bcceb80 is pointing into the stack for thread: 0x000000005af15000
RSI={method} {0x0000000056b83b18} 'run' '()V' in 'TestCaset1000$1'
RDI=0x0000000000000000 is an unknown value
R8 =0x0000000000000000 is an unknown value
R9 =0x0000000000000b80 is an unknown value
R10=0xfefefefefefefeff is an unknown value
R11=0x8080808080808080 is an unknown value
R12=0x000000005af15000 is a thread
R13=0x000000005ae58030 is an unknown value
R14=0x0000000000000000 is an unknown value
R15=0x00000000024388e0 is an unknown value
Stack: [0x000000005bbd0000,0x000000005bcd0000], sp=0x000000005bcce510, free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [jvm.dll+0x12f004]
C [TraceCapture.dll+0x5086] callbackSingleStep+0x466
V [jvm.dll+0x1a7389]
V [jvm.dll+0x1aa34c]
V [jvm.dll+0xa9c23]
C 0x00000000025afddd
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
其中 TraceCapture.dll 就是我寫的 JVMTI agent,大概可以猜測到是 callbackSingleStep+0x466
這個地方出一錯誤,異常
是 java.lang.NoSuchFieldError
。那麼怎麼找到這個位置呢?
這就涉及除錯動態連結庫檔案了,除錯的時候,在 callbackSingleStep 函式上加一個斷點,除錯時會停在這個斷點上。
此時,首先檢視 callbackSingleStep 的首地址,在 visual studio 2013 中把滑鼠放在函式名上就可以顯示出來,然後將這個地址增加
0x466,就找到了出錯的位置的地址,再在 Call Stack 視窗中,右擊 TraceCapture.dll 這一項進行反彙編,就進入了反彙編介面,
找到相應地址對應的函式語句即可。