如何在运行时使用JVMTI获取类文件(采用规范格式)?

时间:2016-12-13 07:02:31

标签: java bytecode-manipulation jvmti jvm-bytecode

我正在开展一个研究项目,其中包括Hotspot profiler的反馈。目前我正在开发一个JVMTI代理,该代理应具有以下功能:

  1. 监听任何已编译的加载事件。
  2. 提取并分析具有热点方法的完整类文件。
  3. 修改/重新定义类的字节码。
  4. 我在JVMTI中有很多API函数可以获取有关JIT编译方法的类文件的信息。但是,我想要java虚拟机规范中描述的方法的完整类文件。 如果无法获得整个类文件,我至少需要一个以下格式的类文件:

    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);
    }
    

    我的问题是:

    1. 是否可以通过代理获取完整的类文件?
    2. 如果没有,我如何使用ClassFileJVMTI API填充JNI结构?
    3. 实现目标的任何其他策略?
    4. 限制:

      1. 我必须在课程加载后很长一段时间内检查和操作字节码。所以使用像ASMJAVASSIST这样的库的AFAIK java代理不会有任何帮助。
      2. 任何帮助都将受到高度赞赏。

1 个答案:

答案 0 :(得分:2)

所以我终于开始工作了。 Holger在评论中的想法是这个答案的主要来源。我不认为有人会需要这个,但只是回答它,这里就是。

简单地说,要获取整个类文件,JVMTI API中只有一种可能性,即ClassFileLoadHook事件。只要在JVM中加载新类或调用RetransformclassesRedefineClasses函数时,就会触发此事件。所以我调用Retransformclasses函数作为一个虚拟调用只是为了调用ClassFileLoadHookEvent然后最终得到整个类。

在我的源代码中添加了以下功能,主要是添加功能和回调设置:

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事件的完整类文件。我分析了这个类文件,并使用char*方法在新的javab_main数组中对其进行了检测,最后将新数组指向new_class_data变量。 new_class_ptr是一个全局变量,它包含已更改的类定义。