动态字节码检测失败,没有任何错误

Dynamic Bytecode Instrumentation fails without any error

Objective

我正在使用 JVMTI 代理进行动态字节码检测。我必须检测那些 "hot" 的方法,即调用 JIT 编译器的方法。为此,我监听 CompiledLoadEvent 并在其回调函数中调用 RetransformClasses。这反过来又会在包含 "hot" 函数的 class 上调用 ClassFileLoadHook,然后开始实际检测。

问题前提

目前我正在使用我的 class 来生成一些线程。我还听线程启动并在我的代理中打印它们。使用简单的 ClassFileLoadHook 在 class 加载时间(没有 RetransformClasses),我的检测工作完美并产生新线程。当 ClassFileLoadHook 仪器在 class 加载时间时,我得到以下输出:

Running Thread: Signal Dispatcher, Priority: 9, context class loader:Not Null
Running Thread: main, Priority: 5, context class loader:Not Null
Running Thread: Thread-0, Priority: 5, context class loader:Not Null
Running Thread: Thread-1, Priority: 5, context class loader:Not Null
Running Thread: Thread-2, Priority: 5, context class loader:Not Null
Running Thread: Thread-3, Priority: 5, context class loader:Not Null
Running Thread: Thread-4, Priority: 5, context class loader:Not Null
Running Thread: Thread-6, Priority: 5, context class loader:Not Null
Running Thread: Thread-5, Priority: 5, context class loader:Not Null
Running Thread: Thread-7, Priority: 5, context class loader:Not Null
Running Thread: DestroyJavaVM, Priority: 5, context class loader:: NULL

当我通过调用 RetransformClasses 然后 ClassFileLoadHook 检测 class 文件时,一切正常,但没有生成线程,因此没有发生有效的检测。 VM甚至需要很长时间才能执行原始代码。

我使用 -XX:+TraceClassLoading 仔细检查了两个仪器。在这两种情况下,所有重新转换的 classes 都被加载。即使是我在运行时生成的 class 也会被加载,但没有检测发生。以下是 class 加载跟踪的输出:

[Loaded Test from __VM_RedefineClasses__]
[Loaded Test_Worker_main_0 from file:/home/saqib/workspace/test/bin]

我在运行时生成第二个 class 并将其加载到 VM 中,但我没有生成任何线程。

问题

代理代码

已编译加载事件回调

static int x = 1;
void JNICALL
compiled_method_load(jvmtiEnv *jvmti, jmethodID method, jint code_size,
        const void* code_addr, jint map_length, const jvmtiAddrLocationMap* map,
        const void* compile_info) {
    jvmtiError err;
    jclass klass;

    char* name = NULL;
    char* signature = NULL;
    char* generic_ptr = NULL;

    err = (*jvmti)->RawMonitorEnter(jvmti, lock);
    check_jvmti_error(jvmti, err, "raw monitor enter");

    err = (*jvmti)->GetMethodName(jvmti, method, &name, &signature,
            &generic_ptr);
    check_jvmti_error(jvmti, err, "Get Method Name");

    printf("\nCompiled method load event\n");
    printf("Method name %s %s %s\n\n", name, signature,
            generic_ptr == NULL ? "" : generic_ptr);

    if (strstr(name, "main") != NULL && x == 1) {
        x++;
        err = (*jvmti)->GetMethodDeclaringClass(jvmti, method, &klass);
        check_jvmti_error(jvmti, err, "Get Declaring Class");

        err = (*jvmti)->RetransformClasses(jvmti, 1, &klass);
        check_jvmti_error(jvmti, err, "Retransform class");

    }

    if (name != NULL) {
        err = (*jvmti)->Deallocate(jvmti, (unsigned char*) name);
        check_jvmti_error(jvmti, err, "deallocate name");
    }
    if (signature != NULL) {
        err = (*jvmti)->Deallocate(jvmti, (unsigned char*) signature);
        check_jvmti_error(jvmti, err, "deallocate signature");
    }
    if (generic_ptr != NULL) {
        err = (*jvmti)->Deallocate(jvmti, (unsigned char*) generic_ptr);
        check_jvmti_error(jvmti, err, "deallocate generic_ptr");
    }

    err = (*jvmti)->RawMonitorExit(jvmti, lock);
    check_jvmti_error(jvmti, err, "raw monitor exit");
}

Class 文件加载挂钩

void JNICALL
Class_File_Load_Hook(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
        jclass class_being_redefined, jobject loader, const char* name,
        jobject protection_domain, jint class_data_len,
        const unsigned char* class_data, jint* new_class_data_len,
        unsigned char** new_class_data) {
    jvmtiError err;
    unsigned char* jvmti_space = NULL;

    if (strstr(name, "Test") != NULL && x == 2) {
        char* args = "op";

        javab_main(2, args, class_data, class_data_len);

        err = (*jvmti_env)->Allocate(jvmti_env, (jlong)global_pos, &jvmti_space);
        check_jvmti_error(jvmti_env, err, "Allocate new class Buffer.");

        (void)memcpy((void*)jvmti_space, (void*)new_class_ptr, (int)global_pos);

        *new_class_data_len = (jint)global_pos;
        *new_class_data = jvmti_space;

        if ( new_class_ptr != NULL ) {
            (void)free((void*)new_class_ptr);
        }

#if DEBUG
        printf("Size of the class is: %d\n", class_data_len);
        for (int i = 0; i < class_data_len; i += 4) {
            if (i % 16 == 0)
                printf("\n");

            printf("%02x%02x  %02x%02x  ", new_class_data[i],
                    new_class_data[i + 1], new_class_data[i + 2],
                    new_class_data[i + 3]);
        }
        printf("\n");
        system("javap -c -v Test_debug");
#endif
        x++;
    }
}

此处 javab_main returns 检测的 char * 数组是正确的。检测数组存储在全局变量 new_class_ptr 中,该变量被复制到 new_class_data 中。为了调试仪器的输出,我还在一个名为 Test_debug 的文件中打印了仪器 class 并在其上调用 javap 产生了所需的结果。

这里给出了完整的代理文件: Agent.c

原码:

            for (int i = 0; i < s; i++)
                for (int j = 0; j < s; j++) {
                    c2[i][j] = 0;
                    for (int k = 0; k < s; k++)
                        c2[i][j] += a[i][k] * b[k][j];
                }

检测代码:(等效)

Thread[] threads = new Thread[NTHREADS];    
            for (int i = 0; i < NTHREADS ; i++) {
                final int lb = i * SIZE/NTHREADS;
                final int ub = (i+1) * SIZE/NTHREADS;
                threads[i] = new Thread(new Runnable() {

                    public void run() {
                        for (int i = lb; i < ub; i++)
                            for (int j = 0; j < SIZE; j++) {
                                c2[i][j] = 0;
                                for (int k = 0; k < SIZE; k++)
                                    c2[i][j] += a[i][k] * b[k][j];
                            }
                    }
                });
                threads[i].start();
            }

            // wait for completion
            for (int i = 0; i < NTHREADS; i++) {
                try {
                    threads[i].join();
                } catch (InterruptedException ignore) {
                }
            }

Java版本

openjdk version "1.8.0-internal-debug"
OpenJDK Runtime Environment (build 1.8.0-internal-debug-saqib_2016_12_26_10_52-b00)
OpenJDK 64-Bit Server VM (build 25.71-b00-debug, mixed mode)

我主要根据评论来构建这个答案。还有一些谜题没有解决,但主要问题已经解决了。在我的例子中,字节码检测 不会失败 。它实际上 从未发生过 。根据理论,

Dynamic bytecode instrumentation of an executing function takes place at subsequent call of the function. If a function has only one invocation, it cannot be instrumented while execution using current hotswap techniques in JVM.

我试图检测一个只有一个函数的 class,即 main。我试图在运行时对其进行检测。这是主要问题。为了检查这个论点的有效性,我试图将我的代码放在另一个函数中,并在循环中从 main 调用它。它得到了检测,一切正常。代理中无需更改任何内容。