Как получить файл класса (в формате спецификации) во время выполнения с помощью JVMTI?

Я работаю над исследовательским проектом, который включает в себя отзывы профилировщика Hotspot. В настоящее время я работаю над агентом JVMTI, который должен иметь следующие функции:

  1. слушайте любое скомпилированное событие загрузки.
  2. Извлеките и проанализируйте полный файл класса, который имеет метод горячей точки.
  3. Изменить/переопределить байт-коды класса.

У меня есть много функций API, доступных в JVMTI, чтобы получить информацию о файле класса, имеющем метод, который компилируется 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. Если нет, то как мне заполнить структуру ClassFile с помощью JVMTI или JNI API?
  3. Любая другая стратегия для достижения моей цели?

Ограничения:

  1. Мне приходится проверять и манипулировать байт-кодами во время выполнения спустя много времени после загрузки класса. Таким образом, java-агенты AFAIK, использующие такие библиотеки, как ASM и JAVASSIST, не помогут.

Любая помощь будет высоко ценится.


person Saqib Ahmed    schedule 13.12.2016    source источник
comment
Вы путаете термины. ASM и JAVASSIST — это просто библиотеки, а не агенты. Как вы их используете, то есть во время загрузки класса или позже, зависит от вас. Но поскольку вы пишете собственный код, использование библиотек, написанных на Java, в любом случае будет довольно сложным. Что касается вашего фактического вопроса, ClassFileLoadHooks получит байтовый код в формате файла класса. Тот факт, что вы хотите получить его для уже загруженных классов, не столь важен, так как, если вы вызовете RetransformClasses, хуки вызовутся снова для указанных классов.   -  person Holger    schedule 14.12.2016
comment
Спасибо, что поправили меня. Я не совсем понял, что вы сказали по поводу моего вопроса. Моя проблема не в том, чтобы получить только байт-код. Я уже делаю это, и это в формате файла класса: (*jvmti)->GetBytecodes(jvmti,method, &code_size, &bytecodes_ptr); Я хочу получить весь файл класса, который имеет постоянный пул, интерфейсы, атрибуты и т. д., как указано в спецификации. Более того, я не хочу слушать ClassFileLoadHook. У меня есть обратный вызов для события CompiledMethodLoad, которому нужно получить всю информацию о классе. Я надеюсь, что это очищает предпосылки моей проблемы.   -  person Saqib Ahmed    schedule 15.12.2016
comment
GetBytecodes возвращает байт-код одного метода. Так это не "в формате class file", ну а иначе в чем был вопрос? Если вы хотите получить весь файл класса, вам подойдет ClassFileLoadHook. Как сказано выше, когда вы вызываете RetransformClasses, хук будет вызываться с указанным классом немедленно. Это способ получить файл класса правильно, когда он вам нужен. Это немного сложно, но это единственный способ, который я знаю.   -  person Holger    schedule 15.12.2016
comment
Между прочим, struct, который вы определили, не является фактическим файлом класса, он имеет только структуру, которая отдаленно напоминает его. Настоящий файл класса не имеет указателей и использует четко определенные типы данных с обратным порядком байтов, которые не обязательно соответствуют представлению short и int во время выполнения на вашем компьютере.   -  person Holger    schedule 15.12.2016
comment
Первый комментарий Хорошо. Я понял. Вы хотите, чтобы я вызвал фиктивную функцию RetransformClasses, чтобы я мог получить ClassFileLoadHook, которая предоставит мне весь файл класса. Было бы здорово, если бы вы могли написать небольшую демонстрацию в качестве ответа, чтобы я мог ее протестировать. Проблема в том, что я не знаю, как передать аргумент const jclass* classes в функцию RetransformClasses. Второй комментарий Я знаю, что это struct не файл класса. Я просто хотел поработать, так как думал, что невозможно получить файл класса.   -  person Saqib Ahmed    schedule 15.12.2016
comment
jclass* — это просто указатель на jclass, который может быть массивом, если вас интересует несколько классов. Так как вас интересует только один класс, вы можете передать указатель на переменную klass, которая уже содержит нужный jclass (после того, как вы вызвали GetMethodDeclaringClass). Таким образом, вы передаете &klass как classes и class_count из 1 (один). Вот и все. В последний раз я писал код на C так давно, что вам потребуется больше времени, чтобы исправить мои ошибки, чем писать самому.   -  person Holger    schedule 15.12.2016
comment
Почему ClassFileLoadHook должен вызывать RetransformClasses? Это уже метод, отвечающий за преобразование кода, если это предусмотрено. Все, что ему нужно сделать, это сохранить ссылку на новый (модифицированный) байт-код в указателе, переданном как параметр new_class_data. Вы читали документацию?   -  person Holger    schedule 15.12.2016


Ответы (1)


Итак, я наконец заработал. Идеи Хольгера в комментариях являются основными источниками этого ответа. Я не думаю, что это кому-то понадобится, но просто чтобы ответить на него, вот.

Проще говоря, чтобы получить весь файл класса, в JVMTI API есть только одна возможность, и это ClassFileLoadHook событие. Это событие запускается всякий раз, когда в JVM загружается новый класс или когда вызываются функции Retransformclasses или RedefineClasses. Поэтому я вызвал функцию 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 — это глобальная переменная, которая содержит измененное определение класса.

person Saqib Ahmed    schedule 16.12.2016