バイトコード表示
JVMTIを使ってJVMエージェントを作ってみる その1ではClassPrepareイベントハンドラを登録するところまでだったけど、今回はmainメソッドのバイトコードを表示するところまでやってみよう。バイトコード表示にはバイトコードを取得する許可を与える必要があるため、AddCapabilitiesを使って、許可を与える。
jvmtiCapabilities capabilities = { 0 }; capabilities.can_get_bytecodes = 1; (*jvmti)->AddCapabilities(jvmti, &capabilities);
また、バイトコードはクラスが準備できた後でなければ取得できないため、ClassPrepareの中で、GetBytecodesを使って取得する。
エージェントのソースコード
jvmtitest.c#include <stdio.h> #include <string.h> #include <jvmti.h> static jvmtiEnv *jvmti = 0; static void JNICALL ClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jclass klass); static jstring getClassName(JNIEnv *env, jclass klass); JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION); jvmtiCapabilities capabilities = { 0 }; capabilities.can_get_bytecodes = 1; (*jvmti)->AddCapabilities(jvmti, &capabilities); jvmtiEventCallbacks callbackTable = { 0 }; callbackTable.ClassPrepare = ClassPrepare; (*jvmti)->SetEventCallbacks(jvmti, &callbackTable, sizeof(callbackTable)); (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); printf("agent is loading...\n"); return 0; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { (*jvmti)->DisposeEnvironment(jvmti); printf("agent is unloading...\n"); } static void JNICALL ClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jclass klass) { jstring strObj = getClassName(jni_env, klass); const char *str = (*jni_env)->GetStringUTFChars(jni_env, strObj, NULL); if (strcmp(str, "local.Main") == 0) { printf("local.Main prepared.\n"); jmethodID mid = (*jni_env)->GetStaticMethodID(jni_env, klass, "main", "([Ljava/lang/String;)V"); jint bc; unsigned char *bcp; (*jvmti_env)->GetBytecodes(jvmti_env, mid, &bc, &bcp); for (int i = 0; i < bc; i++) { printf("%0x\n", bcp[i]); } (*jvmti_env)->Deallocate(jvmti_env, bcp); } (*jni_env)->ReleaseStringUTFChars(jni_env, strObj, str); } static jstring getClassName(JNIEnv *env, jclass klass) { jclass cls = (*env)->GetObjectClass(env, klass); jmethodID mid = (*env)->GetMethodID(env, cls, "getName", "()Ljava/lang/String;"); jstring strObj = (jstring)(*env)->CallObjectMethod(env, klass, mid); return strObj; }
コンパイル&実行
これをコンパイルして、実行すると以下になる。※local/Main.javaの内容は
$ gcc -Wall -std=c99 -shared -fPIC -D_REENTRANT -o libjvmtitest.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux jvmtitest.c $ java -agentpath:./libjvmtitest.so local.Main agent is loading... local.Main prepared. b2 0 2 12 3 b6 0 4 b1 Hello, JVMTI in Java. agent is unloading...
"local.Main prepared."と"Hello, JVMTI in Java."の間に数字がずらずらと出力されている箇所がmainメソッドのバイトコード。
GetBytecodes(jvmti_env, mid, &bc, &bcp)呼び出しでbcpにバイトコードの配列、bcに配列の要素数が入る。
bcpはオペコードとオペランドがそのまま並んでいるので、改行を整理するとこんな感じ。
b2 0 2 12 3 b6 0 4 b1
各命令は以下になる。
getstatic 0 2 ldc 3 invokevirtual 0 4 areturn
これはそれぞれ"Hello, JVMTI in Java."のオペランドスタックへのロードとSystem.out.printlnの呼び出し、メソッドからの復帰に相当している。
Java Virtual Machine Specification
Chapter 6. The Java Virtual Machine Instruction Set
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html
Java仮想マシン#命令セット仕様
https://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3#%E5%91%BD%E4%BB%A4%E3%82%BB%E3%83%83%E3%83%88%E4%BB%95%E6%A7%98
0 件のコメント:
コメントを投稿