动态字节码检测失败,没有任何错误
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 中,但我没有生成任何线程。
问题
- 以我对问题的理解(很有可能
我错了),为什么
ClassFileLoadHook
重新转换 class
在加载期间成功,但不知何故行为不正确
何时调用 JIT?
- 刚写了
RetransformClasses
函数,空
ClassFileLoadHook
回拨,也花了不少时间没有
招致任何类型的错误。什么可能需要时间?
代理代码
已编译加载事件回调
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 调用它。它得到了检测,一切正常。代理中无需更改任何内容。
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 中,但我没有生成任何线程。
问题
- 以我对问题的理解(很有可能
我错了),为什么
ClassFileLoadHook
重新转换 class 在加载期间成功,但不知何故行为不正确 何时调用 JIT? - 刚写了
RetransformClasses
函数,空ClassFileLoadHook
回拨,也花了不少时间没有 招致任何类型的错误。什么可能需要时间?
代理代码
已编译加载事件回调
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 调用它。它得到了检测,一切正常。代理中无需更改任何内容。