ページ更新: 2009-10-29 (木) (3496日前)

関連: ソフト/Graphviz

(2005-11-05 新規作成)

JVMTIを使う のTraceAgentを少々改造してみた。

未完成。

目次

[編集]

課題 #

[編集]

改造個所 #

* メソッドのEnter,Exitをトレースする (<- Enterのみ)
* ログをカレントディレクトリのtrace.logに出力 (<- 標準出力)
* 出力を若干追加
[編集]

ログの書式 #

各フィールドはカンマで区切っている

1. スレッド名
2. Enterなら'>', Exitなら'<'
3. クラスのシグニチャ
4. メソッド名
5. メソッドのシグニチャ
[編集]

使い方 #

java -agentlib:{path/to/TraceAgent.dll} HelloWorld
[編集]

コンパイル方法 #

[編集]

Windows, Cygwin GCC #

-Wl,--enable-auto-import
-Wl,--add-stdcall-alias 
[編集]

Windows, VisualStudio.NET 2003 #

[編集]

Linux, GCC #

[編集]

コード #

/**
 * TraceAgent.c
 * メソッド実行トレース簡単JVMTIエージェント
 */
#include <jvmti.h>
#include <stdio.h>
#include <string.h>

static char* LOG_FILE = "trace.log";
static char* PACKAGE_FILTER = "jp.discypus.";  /* このパッケージだけトレースする */

static FILE *fp;

static jvmtiEnv* jvmti = NULL;
static jvmtiCapabilities capa;

static void putMethodCallInfo(
    jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jmethodID method,
    char* direction
) {
    jvmtiError error;
    char* name = NULL;
    char* msig = NULL;
    char* gmsig = NULL;
    char* csig = NULL;
    char* gcsig = NULL;
    jclass clazz;
    jvmtiThreadInfo threadInfo;

    threadInfo.name = NULL;

    /* メソッドの属するクラス */
    error = (*jvmti)->GetMethodDeclaringClass(jvmti, method, &clazz);
    if (error != JVMTI_ERROR_NONE) {
        printf("GetMethodDeclaringClass:%d\n", error);
        goto exit;
    }

    /* クラスのシグニチャ */
    error = (*jvmti)->GetClassSignature(jvmti, clazz, &csig, &gcsig);
    if (error != JVMTI_ERROR_NONE) {
        printf("GetClassSignature:%d\n", error);
        goto exit;
    }
    

    /* パッケージ名にPACKAGE_FILTERがが含まれていない */
    if (strstr(csig, PACKAGE_FILTER) == NULL) {
        goto exit;
    }

    /* メソッド名、シグニチャ */
    error = (*jvmti)->GetMethodName(jvmti, method, &name, &msig, &gmsig);
    if (error != JVMTI_ERROR_NONE) {
        printf("GetMethodName:%d\n", error);
        goto exit;
    }

    if (strstr(name, "<clinit>") != NULL) {
        goto exit;
    } else if (strstr(name, "<init>") != NULL) {
        goto exit;
    }

    /* スレッド情報 */
    error = (*jvmti)->GetThreadInfo(jvmti, thread, &threadInfo);
    if (error != JVMTI_ERROR_NONE) {
        printf("GetThreadInfo:%d\n", error);
        goto exit;
    }

    fprintf(fp, "%s,%s,%s,%s,%s\n", 
        threadInfo.name, direction, csig, name, msig);

exit:
    if (csig != NULL) {
        (*jvmti)->Deallocate(jvmti, csig);
    }
    if (gcsig != NULL) {
        (*jvmti)->Deallocate(jvmti, gcsig);
    }
    if (msig != NULL) {
        (*jvmti)->Deallocate(jvmti, msig);
    }
    if (gmsig != NULL) {
        (*jvmti)->Deallocate(jvmti, gmsig);
    }
    if (threadInfo.name != NULL) {
        (*jvmti)->Deallocate(jvmti, threadInfo.name);
    }
}


/**
 * JavaVM上でメソッドが実行されたら呼び出される。
 * 
 */
static void JNICALL
methodEntry(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jmethodID method) {
    putMethodCallInfo(jvmti, env, thread, method, ">");
}

/**
 * JavaVM上でメソッドが実行されたら呼び出される。
 * 
 */
static void JNICALL
methodExit(jvmtiEnv *jvmti, JNIEnv* env, jthread thread, jmethodID method,
  jboolean was_popped_by_exception, jvalue return_value) {

    if (was_popped_by_exception == JNI_FALSE) {
        putMethodCallInfo(jvmti, env, thread, method, "<");
    }
}

/**
 * JavaVMが初期化される際に呼び出されるコールバック関数。
 * Agent_OnLoad内でコールバックとして登録している。
 */
static void JNICALL vmInit(jvmtiEnv* jvmti, JNIEnv* env, jthread thread) {
    fp = fopen(LOG_FILE, "w");

    printf("JVMTI_EVENT_VM_INIT notified\n");

    (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL);
    (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, NULL);
}

/**
 * JVMTIエージェントが含まれたライブラリがロードされた際に呼び出される。
 * options に'abort'が指定されると、JNI_ERRを返却する
 */
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
    jvmtiError error;
    jint ret;
    jvmtiEventCallbacks callbacks;

    printf("Hello, JVMTI. option '%s' specified\n", options);

    ret = (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION_1_0);
    if (ret != JNI_OK || jvmti == NULL) {
        printf("ERROR: Unable to access JVMTI Version 1 (0x%x),"
            "is your J2SE a 1.5 or newer version?"
            "JNIEnv's GetEnv() returned %d\n", JVMTI_VERSION_1, ret);
    }

    (void)memset(&capa, 0, sizeof(jvmtiCapabilities));
    capa.can_generate_method_entry_events = 1;
    capa.can_generate_method_exit_events = 1;
    error = (*jvmti)->AddCapabilities(jvmti, &capa);
    if (error != JVMTI_ERROR_NONE) {
        printf("AddCapabilities:%d\n", error);
        return JNI_ERR;
    }

    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.VMInit = &vmInit;
    callbacks.MethodEntry = &methodEntry;
    callbacks.MethodExit = &methodExit;
    (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));

    (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);

    return JNI_OK;
}

/**
 * JavaVMが終了する際に呼び出される。
 */
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm) {
    printf("Good-bye, JVMTI.\n");

    fclose(fp);
}
[編集]

補足 #

[編集]

Limpid Log #

Java2 SE 5.0以後であれば、メソッドのトレースはjava.lang.instrumentsを使って、Java/instrument#LimpidLogで出来るようだ。