Найти имена всех классов, которые программа Java загружает с помощью отражения

Чтобы поддерживать инструмент статического анализа, я хочу инструментировать или контролировать программу Java таким образом, чтобы я мог определить для каждого рефлексивного вызова (например, Method.invoke(..)):

1.) для какого класса C вызывается этот метод, и 2.) какой загрузчик классов загрузил этот класс C.

В идеале я ищу решение, которое не требует от меня статического изменения библиотеки времени выполнения Java, т.е. я ищу решение во время загрузки. Однако решение должно иметь возможность захватывать все отражающие вызовы, даже такие вызовы, которые происходят в самой библиотеке времени выполнения Java. (Я играл с ClassFileTransformer, но, похоже, это применяется только к классам, от которых сам ClassFileTransformer не зависит. В частности, ClassFileTransformer не применяется к классу «Класс».)

Спасибо!


person user66237    schedule 29.08.2009    source источник


Ответы (2)


Вы ищете что-то, что может работать в продакшене? Или достаточно настроить приложение, работающее в тестовой среде? Если это последнее, возможно, следует рассмотреть возможность запуска приложения с помощью инструмента профилирования. Я лично использовал и рекомендую JProfiler, который позволяет отслеживание вызовов и настройка триггеров для выполнения таких действий, как ведение журнала при вызове определенных методов. Он не требует каких-либо изменений в размещенной программе и прекрасно работает с библиотекой времени выполнения Java. Существуют также инструменты с открытым исходным кодом, но мне не удалось заставить их работать.

Если вам нужно что-то, что будет работать в продакшене, вы можете изучить реализацию собственного пользовательского загрузчика классов или манипулирование байтовым кодом с помощью Javassist или CGLib, возможно, с использованием AspectJ (AOP). Очевидно, это более сложное решение, и я не уверен, что оно будет работать без поддержки во время компиляции, поэтому, надеюсь, инструмент профилирования подойдет для вашей ситуации.

person Rob H    schedule 30.08.2009

API, который вам, вероятно, нужен, это JVMTI. JVMTI позволяет регистрировать обратные вызовы для большинства событий, происходящих внутри JVM, включая MethodEntry, MethodExit. Вы слушаете эти события и извлекаете события Method.invoke. Существуют вызовы API для получения загрузчика классов для определенного класса. Однако вам придется написать инструмент на C или C++.

Вот пример, который выведет фильтр из вызова java.lang.reflect.Method.invoke и распечатает его. Чтобы получить подробную информацию о вызываемом объекте, вам, вероятно, потребуется просмотреть кадр стека.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <jvmti.h>

static jvmtiEnv *jvmti = NULL;
static jvmtiCapabilities capa;

static jint check_jvmti_error(jvmtiEnv *jvmti,
                              jvmtiError errnum,
                              const char *str) {

    if (errnum != JVMTI_ERROR_NONE) {
        char *errnum_str;
        errnum_str = NULL;
        (void) (*jvmti)->GetErrorName(jvmti, errnum, &errnum_str);
        printf("ERROR: JVMTI: %d(%s): %s\n",
               errnum,
               (errnum_str == NULL ? "Unknown" : errnum_str),
               (str == NULL ? "" : str));
        return JNI_ERR;
    }
    return JNI_OK;
}

void JNICALL callbackMethodEntry(jvmtiEnv *jvmti_env,
                                 JNIEnv* jni_env,
                                 jthread thread,
                                 jmethodID method) {

    char* method_name;
    char* method_signature;
    char* generic_ptr_method;
    char* generic_ptr_class;
    char* class_name;
    jvmtiError error;
    jclass clazz;

    error = (*jvmti_env)->GetMethodName(jvmti_env,
                                        method,
                                        &method_name,
                                        &method_signature,
                                        &generic_ptr_method);
    if (check_jvmti_error(jvmti_env, error, "Failed to get method name")) {
        return;
    }

    if (strcmp("invoke", method_name) == 0) {

        error
                = (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method,
                        &clazz);
        if (check_jvmti_error(jvmti_env, error,
                "Failed to get class for method")) {
            (*jvmti_env)->Deallocate(jvmti_env, method_name);
            (*jvmti_env)->Deallocate(jvmti_env, method_signature);
            (*jvmti_env)->Deallocate(jvmti_env, generic_ptr_method);
            return;
        }

        error = (*jvmti_env)->GetClassSignature(jvmti_env, clazz, &class_name,
                &generic_ptr_class);
        if (check_jvmti_error(jvmti_env, error, "Failed to get class signature")) {
            (*jvmti_env)->Deallocate(jvmti_env, method_name);
            (*jvmti_env)->Deallocate(jvmti_env, method_signature);
            (*jvmti_env)->Deallocate(jvmti_env, generic_ptr_method);
            return;
        }

        if (strcmp("Ljava/lang/reflect/Method;", class_name) == 0) {
            printf("Method entered: %s.%s.%s\n", class_name, method_name,
                    method_signature);
        }
        (*jvmti_env)->Deallocate(jvmti_env, class_name);
        (*jvmti_env)->Deallocate(jvmti_env, generic_ptr_class);
    }

    (*jvmti_env)->Deallocate(jvmti_env, method_name);
    (*jvmti_env)->Deallocate(jvmti_env, method_signature);
    (*jvmti_env)->Deallocate(jvmti_env, generic_ptr_method);
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {

    jint result;
    jvmtiError error;
    jvmtiEventCallbacks callbacks;

    result = (*jvm)->GetEnv(jvm, (void**) &jvmti, JVMTI_VERSION_1_0);
    if (result != JNI_OK || jvmti == NULL) {
        printf("error\n");
        return JNI_ERR;
    } else {
        printf("loaded agent\n");
    }

    (void) memset(&capa, 0, sizeof(jvmtiCapabilities));
    capa.can_generate_method_entry_events = 1;

    error = (*jvmti)->AddCapabilities(jvmti, &capa);
    if (check_jvmti_error(jvmti, error, "Unable to set capabilities") != JNI_OK) {
        return JNI_ERR;
    }

    (void) memset(&callbacks, 0, sizeof(callbacks));
    callbacks.MethodEntry = &callbackMethodEntry;
    error = (*jvmti)->SetEventCallbacks(jvmti,
                                        &callbacks,
                                        (jint) sizeof(callbacks));
    if (check_jvmti_error(jvmti, error, "Unable to set callbacks") != JNI_OK) {
        return JNI_ERR;
    }

    error = (*jvmti)->SetEventNotificationMode(jvmti,
                                               JVMTI_ENABLE,
                                               JVMTI_EVENT_METHOD_ENTRY,
                                               (jthread) NULL);
    if (check_jvmti_error(jvmti, error,
            "Unable to set method entry notifications") != JNI_OK) {
        return JNI_ERR;
    }

    return JNI_OK;
}
person Michael Barker    schedule 31.08.2009