1. 程式人生 > >JVMTI 中的JNI系列函式,執行緒安全及除錯技巧

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 中有一系列回撥函式,當使用 SetEventNotificationModeSetEventCallbacks 設定了某個事件的回撥函式後,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 這一項進行反彙編,就進入了反彙編介面,
找到相應地址對應的函式語句即可。

JVMTI 中的JNI系列函式,執行緒安全及除錯技巧