2

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.

Saqib Ahmed
  • 1,056
  • 14
  • 33
  • You are mixing up terms. ASM and JAVASSIST are just libraries, not agents. How you use them, i.e. during class loading or later-on, is up to you. But since you are writing native code, using libraries written in Java would be quite complicated anyway. Regarding your actual question, `ClassFileLoadHook`s will receive the byte code in the class file format. The fact that you want to get it for already loaded classes, is not so important, as, if you invoke `RetransformClasses`, the hooks will get called again for the specified classes. – Holger Dec 14 '16 at 12:26
  • Thank you for correcting me. I couldn't quite get what you said about my question though. My problem is not to get the bytecode only. I'm already doing that and it is in class file format: `(*jvmti)->GetBytecodes(jvmti,method, &code_size, &bytecodes_ptr);` I want to get whole class file which has constant pool, interfaces, attributes etc as mentioned in the spec. Moreover, I don't wanna listen `ClassFileLoadHook`. I have a call back for `CompiledMethodLoad` event which needs to get all the class information. I hope it clears the premises of my problem. – Saqib Ahmed Dec 15 '16 at 04:16
  • `GetBytecodes` returns the byte code of a single method. So it’s not “in class file format”, well otherwise, what was the question? If you want to get the entire class file, `ClassFileLoadHook` is the way to go. As said above, when you call `RetransformClasses`, the hook will be called with the specified class immediately. That’s a way to get the class file right when you need it. It’s a bit complicated, but it’s the only way I know. – Holger Dec 15 '16 at 09:37
  • By the way, the `struct` you have defined is not an actual class file, it only has a structure that loosely resembles it. A real class file has no pointers and uses well defined big endian datatypes which do not necessarily match the runtime representation of `short` and `int` on your machine. – Holger Dec 15 '16 at 09:45
  • **First Comment** Ok. I got it now. You want me to call a dummy `RetransformClasses` function so that I can get a `ClassFileLoadHook` which will provide me the whole class file. It would be a great help if you can please write a small demonstration as an answer so that I can test it. Problem is I don't know how to provide `const jclass* classes` argument in the `RetransformClasses` function. **Second Comment** I know this `struct` is not a class file. I just wanted to have a work around since I thought that it was not possible to get the class file. – Saqib Ahmed Dec 15 '16 at 10:29
  • `jclass*` is just a pointer to a `jclass`, which could be an array, if you are interested in multiple classes. Since you are interested in only one class, you can pass a pointer to the `klass` variable, which already contains the desired `jclass` (after you called `GetMethodDeclaringClass`). So you pass `&klass` as `classes` and a `class_count` of `1` (one). That’s it. The last time, I wrote C code, is so long ago, you would need more time to fix my mistakes than to write it yourself. – Holger Dec 15 '16 at 10:36
  • 1
    Why should a `ClassFileLoadHook` invoke `RetransformClasses`? It is already the method responsible for transforming code, if intended. All it has to do, is to store a reference to the new (modified) byte code in the pointer passed as `new_class_data` parameter. Did you read [the documentation](https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#ClassFileLoadHook)? – Holger Dec 15 '16 at 14:11

1 Answers1

4

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.

Saqib Ahmed
  • 1,056
  • 14
  • 33