如何使用 JVMTI 在运行时获取 class 文件(规范格式)?
How to get a class file (in specification format) during runtime using JVMTI?
我正在进行一个研究项目,其中包括 Hotspot profiler 的反馈。目前我正在开发一个 JVMTI 代理,它应该具有以下功能:
- 监听任何已编译的加载事件。
- 提取并分析具有热点方法的完整 class 文件。
- Modify/Redefine class.
的字节码
我在 JVMTI 中有很多可用的 API 函数来获取有关具有由 JIT 编译的方法的 class 文件的信息。但是,我想要 java 虚拟机规范中描述的方法的完整 class 文件。
如果不可能得到一个完整的 class 文件,我至少想要一个格式如下的 class 文件:
typedef struct {
unsigned int magic;
unsigned short minor_version;
unsigned short major_version;
unsigned short constant_pool_count;
unsigned char *constant_pool;
unsigned short access_flags;
unsigned short this_class;
unsigned short super_class;
unsigned short interfaces_count;
unsigned char *interfaces;
unsigned short fields_count;
unsigned char *fields;
unsigned short methods_count;
unsigned char *methods;
unsigned short attributes_count;
unsigned char *attributes;
}ClassFile;
到目前为止,我有以下代码部分用于此目的:
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)
{
static ClassFile *clazz;
jvmtiError err;
jclass klass;
jint constant_pool_count_pointer;
jint constant_pool_byte_count_pointer;
jint local_entry_count_ptr;
jint minor, major;
jint modifier_ptr;
jvmtiLocalVariableEntry* table_ptr;
unsigned char* constant_pool_bytes_ptr;
char* name = NULL;
char* signature = NULL;
char* generic_ptr = NULL;
unsigned char* bytecodes_ptr = NULL;
err = (*jvmti)->RawMonitorEnter(jvmti,lock);
check_jvmti_error(jvmti, err, "raw monitor enter");
clazz->magic = 0xCAFEBABE;
err = (*jvmti)->GetMethodDeclaringClass(jvmti,method, &klass);
check_jvmti_error(jvmti, err, "Get Declaring Class");
err = (*jvmti)->GetClassVersionNumbers(jvmti, klass, &minor, &major);
check_jvmti_error(jvmti, err, "Get Class Version Number");
clazz->minor_version = (u2_int)minor;
clazz->major_version = (u2_int)major;
err = (*jvmti)->GetConstantPool(jvmti, klass, &constant_pool_count_pointer,
&constant_pool_byte_count_pointer, &constant_pool_bytes_ptr);
check_jvmti_error(jvmti, err, "Get Constant Pool");
clazz->constant_pool_count = constant_pool_count_pointer;
clazz->constant_pool = constant_pool_bytes_ptr;
err = (*jvmti)->GetClassModifiers(jvmti,klass, &modifier_ptr);
check_jvmti_error(jvmti, err, "Get Access Flags");
clazz->access_flags = (u2_int)modifier_ptr;
err = (*jvmti)->GetBytecodes(jvmti,method, &code_size, &bytecodes_ptr);
check_jvmti_error(jvmti, err, "Get Bytecodes");
err = (*jvmti)->GetLocalVariableTable(jvmti,method, &local_entry_count_ptr, &table_ptr);
check_jvmti_error(jvmti, err, "Get Local Variable table");
if (constant_pool_bytes_ptr != NULL) {
err = (*jvmti)->Deallocate(jvmti,(unsigned char*)constant_pool_bytes_ptr);
check_jvmti_error(jvmti, err, "deallocate bytecodes pointer");
}
if (bytecodes_ptr != NULL) {
err = (*jvmti)->Deallocate(jvmti,(unsigned char*)bytecodes_ptr);
check_jvmti_error(jvmti, err, "deallocate bytecodes pointer");
}
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);
}
我的问题是:
- 是否可以通过代理获得完整的class文件?
- 如果没有,我如何使用
JVMTI
或JNI
API填充ClassFile
结构?
- 还有其他实现我的 objective 的策略吗?
限制:
- 我必须在 class 加载后的很长一段时间内在运行时检查和操作字节码。所以 AFAIK java 代理使用像
ASM
和 JAVASSIST
这样的库不会有任何帮助。
如有任何帮助,我们将不胜感激。
所以我终于让它工作了。霍尔格在评论中的想法是这个答案的主要来源。我认为没有人会需要这个,但只是为了回答这个问题,这里是。
简单来说,要获取整个class文件,在JVMTI
API中只有一种可能,那就是ClassFileLoadHook
事件。每当在 JVM 中加载新的 class 或调用 Retransformclasses
或 RedefineClasses
函数时,都会触发此事件。所以我调用 Retransformclasses
函数作为虚拟调用只是为了调用 ClassFileLoadHookEvent
然后最终得到了整个 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;
char* args = "vop";
javab_main(3, 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;
}
此处的 class_data
变量包含调用 ClassFileLoadHook
事件的完整 class 文件。我分析了这个 class 文件并使用 javab_main
方法在一个新的 char*
数组中对其进行了检测,最后将新数组指向 new_class_data
变量。 new_class_ptr
是一个全局变量,其中包含 class 的更改定义。
我正在进行一个研究项目,其中包括 Hotspot profiler 的反馈。目前我正在开发一个 JVMTI 代理,它应该具有以下功能:
- 监听任何已编译的加载事件。
- 提取并分析具有热点方法的完整 class 文件。
- Modify/Redefine class. 的字节码
我在 JVMTI 中有很多可用的 API 函数来获取有关具有由 JIT 编译的方法的 class 文件的信息。但是,我想要 java 虚拟机规范中描述的方法的完整 class 文件。 如果不可能得到一个完整的 class 文件,我至少想要一个格式如下的 class 文件:
typedef struct {
unsigned int magic;
unsigned short minor_version;
unsigned short major_version;
unsigned short constant_pool_count;
unsigned char *constant_pool;
unsigned short access_flags;
unsigned short this_class;
unsigned short super_class;
unsigned short interfaces_count;
unsigned char *interfaces;
unsigned short fields_count;
unsigned char *fields;
unsigned short methods_count;
unsigned char *methods;
unsigned short attributes_count;
unsigned char *attributes;
}ClassFile;
到目前为止,我有以下代码部分用于此目的:
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)
{
static ClassFile *clazz;
jvmtiError err;
jclass klass;
jint constant_pool_count_pointer;
jint constant_pool_byte_count_pointer;
jint local_entry_count_ptr;
jint minor, major;
jint modifier_ptr;
jvmtiLocalVariableEntry* table_ptr;
unsigned char* constant_pool_bytes_ptr;
char* name = NULL;
char* signature = NULL;
char* generic_ptr = NULL;
unsigned char* bytecodes_ptr = NULL;
err = (*jvmti)->RawMonitorEnter(jvmti,lock);
check_jvmti_error(jvmti, err, "raw monitor enter");
clazz->magic = 0xCAFEBABE;
err = (*jvmti)->GetMethodDeclaringClass(jvmti,method, &klass);
check_jvmti_error(jvmti, err, "Get Declaring Class");
err = (*jvmti)->GetClassVersionNumbers(jvmti, klass, &minor, &major);
check_jvmti_error(jvmti, err, "Get Class Version Number");
clazz->minor_version = (u2_int)minor;
clazz->major_version = (u2_int)major;
err = (*jvmti)->GetConstantPool(jvmti, klass, &constant_pool_count_pointer,
&constant_pool_byte_count_pointer, &constant_pool_bytes_ptr);
check_jvmti_error(jvmti, err, "Get Constant Pool");
clazz->constant_pool_count = constant_pool_count_pointer;
clazz->constant_pool = constant_pool_bytes_ptr;
err = (*jvmti)->GetClassModifiers(jvmti,klass, &modifier_ptr);
check_jvmti_error(jvmti, err, "Get Access Flags");
clazz->access_flags = (u2_int)modifier_ptr;
err = (*jvmti)->GetBytecodes(jvmti,method, &code_size, &bytecodes_ptr);
check_jvmti_error(jvmti, err, "Get Bytecodes");
err = (*jvmti)->GetLocalVariableTable(jvmti,method, &local_entry_count_ptr, &table_ptr);
check_jvmti_error(jvmti, err, "Get Local Variable table");
if (constant_pool_bytes_ptr != NULL) {
err = (*jvmti)->Deallocate(jvmti,(unsigned char*)constant_pool_bytes_ptr);
check_jvmti_error(jvmti, err, "deallocate bytecodes pointer");
}
if (bytecodes_ptr != NULL) {
err = (*jvmti)->Deallocate(jvmti,(unsigned char*)bytecodes_ptr);
check_jvmti_error(jvmti, err, "deallocate bytecodes pointer");
}
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);
}
我的问题是:
- 是否可以通过代理获得完整的class文件?
- 如果没有,我如何使用
JVMTI
或JNI
API填充ClassFile
结构? - 还有其他实现我的 objective 的策略吗?
限制:
- 我必须在 class 加载后的很长一段时间内在运行时检查和操作字节码。所以 AFAIK java 代理使用像
ASM
和JAVASSIST
这样的库不会有任何帮助。
如有任何帮助,我们将不胜感激。
所以我终于让它工作了。霍尔格在评论中的想法是这个答案的主要来源。我认为没有人会需要这个,但只是为了回答这个问题,这里是。
简单来说,要获取整个class文件,在JVMTI
API中只有一种可能,那就是ClassFileLoadHook
事件。每当在 JVM 中加载新的 class 或调用 Retransformclasses
或 RedefineClasses
函数时,都会触发此事件。所以我调用 Retransformclasses
函数作为虚拟调用只是为了调用 ClassFileLoadHookEvent
然后最终得到了整个 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;
char* args = "vop";
javab_main(3, 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;
}
此处的 class_data
变量包含调用 ClassFileLoadHook
事件的完整 class 文件。我分析了这个 class 文件并使用 javab_main
方法在一个新的 char*
数组中对其进行了检测,最后将新数组指向 new_class_data
变量。 new_class_ptr
是一个全局变量,其中包含 class 的更改定义。