views:

308

answers:

2

To support a static-analysis tool I want to instrument or monitor a Java program in such a way that I can determine for every reflective call (like Method.invoke(..)):

1.) which class C this method is invoked on, and 2.) which classloader loaded this class C.

Ideally I am looking for a solution that does not require me to statically modify the Java Runtime Library, i.e. I am looking for a load-time solution. However, the solution should be able to capture all reflective calls, even such calls that occur within the Java Runtime Library itself. (I played around with ClassFileTransformer but this seems to be applied only to classes that ClassFileTransformer itself does not depend on. In particular, a ClassFileTransfomer is not applied to the class "Class".)

Thanks!

+1  A: 

Are you looking for something that can run in production? Or is it sufficient to instrument the application running in a test environment? If it's the latter, might want to consider running the application under a profiling tool. I've personally used and would recommend JProfiler, which lets you do call tracing and set up triggers to perform actions like logging when specific methods are invoked. It does not require any modifications to the hosted program and works just fine on the Java runtime library. There are open source tools, too, but I have not had as much success getting those to work.

If you need something that will run in production, you might want to investigate implementing your own custom Classloader or byte code manipulation via Javassist or CGLib, perhaps using AspectJ (AOP). That's obviously a more complication solution and I'm not sure it'll work without compile-time support, so hopefully the profiling tool is feasible for your situation.

Rob H
A: 

The API that you are probably after is JVMTI. JVMTI allows you to register callbacks for the majority of events that occur within the JVM, including MethodEntry, MethodExit. You listen for those events and pull out the Method.invoke events. There are API calls to get the classloader for a specific class. However you will have to write tool in C or C++.

Here is an example that will get the filter out the java.lang.reflect.Method.invoke call and print it out. To get details regarding the object that has been called you will probably need to look at the stack frame.

#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;
}
Michael Barker