Saqib Ahmed
Saqib Ahmed

Reputation: 1125

How to get a class file (in specification format) during runtime using JVMTI?

I am working on a research project which includes Hotspot profiler's feedback. Currently I am working on a JVMTI agent which should have following features:

  1. listen any compiled load event.
  2. Extract and analyse the complete class file which has hotspot method.
  3. Modify/Redefine the bytecodes of the class.

I have a lot of API functions available in JVMTI to get the information about the class file having the method which is being compiled by JIT. However, I want the complete class file of the method as described in java virtual machine specification. If it is not possible to get a whole class file, I would at least want a class file in following format:

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;

I have following code so far which serves the purpose partially:

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

My questions are:

  1. Is it possible to get complete class file through an agent?
  2. If not, how can I fill the ClassFile structure using JVMTI or JNI API?
  3. Any other strategy to achieve my objective?

Limitations:

  1. I have to examine and manipulate bytecodes during runtime a long time after class loading. So AFAIK java agents using libraries like ASM and JAVASSIST wouldn't be of any help.

Any help would be highly appreciated.

Upvotes: 2

Views: 1345

Answers (1)

Saqib Ahmed
Saqib Ahmed

Reputation: 1125

So I finally got it working. The ideas from Holger in the comments are the main sources of this answer. I don't think anybody would need this, but just to answer it, here goes.

Simply put, to get the whole class file, there is only one possibility in JVMTI APIs and that is ClassFileLoadHook event. This event is triggered whenever a new class is being loaded in JVM or when Retransformclasses or RedefineClasses functions are called. So I called Retransformclasses function as a dummy call just to invoke ClassFileLoadHookEvent and then finally got the whole class.

Added following function in my source code mainly besides adding capabilities and callback setups:

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

The class_data variable here contains the complete class file on which the ClassFileLoadHook event was invoked. I analyzed this class file and instrumented it in a new char* array using javab_main method and finally pointed the new array towards new_class_data variable. The new_class_ptr is a global variable which contains the changed definition of the class.

Upvotes: 4

Related Questions