Search code examples
javajava-native-interfacejvmti

How to Get the values of method local variables and class variables using jvmti


I am trying to capture the variable values using JVMTI, when an exception event is generated, i went through the jvmti documentation and found that there are no functions which let me retrieve the values of the fields(variables), how can this be achieved ?

Below is the Agent code:

#include<jni.h>
#include<jvmti.h>
#include<string.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct {
jvmtiEnv *jvmti;
jrawMonitorID lock;
} GlobalAgentData;

static GlobalAgentData *gdata;

static bool 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 false;
   }
   return true;
 }

static void deallocate(jvmtiEnv *jvmti,void *ptr){
jvmtiError error;
error = (*jvmti)->Deallocate(jvmti,ptr);
check_jvmti_error(jvmti,error,"Cannot deallocate memory");
}

static void allocate(jvmtiEnv *jvmti,jint len){
jvmtiError error;
void *ptr;
error = (*jvmti)->Allocate(jvmti,len,(unsigned char **)&ptr);
check_jvmti_error(jvmti,error,"Cannot allocate memory");
}


JNICALL jint objectCountingCallback(jlong class_tag,jlong size,jlong* tag_ptr,jint length,void* user_data){
    int* count = (int*)user_data;
    *count+=1;
    return JVMTI_VISIT_OBJECTS; 
}

JNIEXPORT jint JNICALL Java_Test_countInstances(JNIEnv *env,jclass thisClass,jclass klass){
    int count =0 ;
    jvmtiError error;
    jvmtiHeapCallbacks callbacks;
jvmtiEnv *jvmti;
    (void)memset(&callbacks,0,sizeof(callbacks));
    callbacks.heap_iteration_callback = &objectCountingCallback;
    jvmti = gdata->jvmti;
error = (*jvmti)->IterateThroughHeap(jvmti,0,klass,&callbacks,&count);
//  check_jvmti_error(*gdata->jvmti,error,"Unable to iterate through the heap");
    return count;
}

static void enter_critical_section(jvmtiEnv *jvmti){
jvmtiError error;
error = (*jvmti)->RawMonitorEnter(jvmti,gdata->lock);
check_jvmti_error(jvmti,error,"Cannot enter with raw monitor");
}

static void exit_critical_section(jvmtiEnv *jvmti){
jvmtiError error;
error = (*jvmti)->RawMonitorExit(jvmti,gdata->lock);
check_jvmti_error(jvmti,error,"Cannot exit with raw monitor");
}

static void JNICALL callbackVMInit(jvmtiEnv *jvmti,JNIEnv *env,jthread thread){
jvmtiError error;
//  enter_critical_section(jvmti);{ /* not needed since we are just setting event notifications */
printf("Initializing JVM\n");
error = (*jvmti)->SetEventNotificationMode(jvmti,JVMTI_ENABLE,JVMTI_EVENT_EXCEPTION,(jthread)NULL);
//  error = (*jvmti)->SetEventNotificationMode(jvmti,JVMTI_ENABLE,JVMTI_EVENT_METHOD_ENTRY,(jthread)NULL);
check_jvmti_error(jvmti,error,"Cannot set Exception Event notification");
//  } exit_critical_section(jvmti);
}

static void callbackMethodEntry(jvmtiEnv *jvmti,JNIEnv *env,jthread thread,jmethodID method){
jvmtiError error;
jvmtiLocalVariableEntry **table_ptr;
jint count,entry_count_ptr;
jobject value_ptr;
int j;
error = (*jvmti)->GetLocalVariableTable(jvmti,method,&entry_count_ptr,table_ptr);
if(check_jvmti_error(jvmti,error,"Cannot Get Local Variable table\n")){
     printf("local variable table entry size : %d %s\n",entry_count_ptr,(*table_ptr)[0].name);          
}
// for(j=0;j<*entry_count_ptr;j++){
 //     error = (*jvmti)->GetLocalObject(jvmti,thread,0,(*table_ptr)[j].slot,&value_ptr);
 //        printf("Field Name:%s\n",(*table_ptr)[j].name);
 //    } 
}


static void JNICALL callbackException(jvmtiEnv *jvmti,JNIEnv *env,jthread thread,jmethodID method,jlocation location,jobject exception,jmethodID catch_method,jlocation catch_location){
jvmtiFrameInfo frames[10];
jint count,entry_count_ptr;
int i,j;
jvmtiError error;
jobject* value_ptr;
char *name,*sig,*gsig;
jclass declaring_class_ptr;
jvmtiLocalVariableEntry *table_ptr;


error = (*jvmti)->GetStackTrace(jvmti,thread,0,10,frames,&count);
if(check_jvmti_error(jvmti,error,"Cannot Get Frame") && count >=1){
    char *methodName,*className;
    for(i=0;i<count;i++){
        error = (*jvmti)->GetMethodName(jvmti, frames[i].method,&methodName,&sig,&gsig);
        if(check_jvmti_error(jvmti,error,"Cannot Get method name")){
            error = (*jvmti)->GetMethodDeclaringClass(jvmti,frames[i].method,&declaring_class_ptr);
            check_jvmti_error(jvmti,error,"Cannot Get method declaring class");
            error = (*jvmti)->GetClassSignature(jvmti,declaring_class_ptr,&className,NULL);
            check_jvmti_error(jvmti,error,"Cannot get class signature");
            // printf("Got Exception in  Method: %s at Line: %ld with Signature:%s,%s within Class:%s\n",methodName,frames[i].location,sig,gsig,className);

            for(j=0;j<entry_count_ptr;j++){
                callbackMethodEntry(jvmti,env,thread,frames[j].method);
                error = (*jvmti)->GetLocalObject(jvmti,thread,i,table_ptr[j].slot,value_ptr);// change the value of the slot parameter
                printf("Field Name:%s\n",table_ptr[j].name);
             }

        }
    }
}

/*  error = (*jvmti)->GetMethodName(jvmti,method,&name,&sig,&gsig);
check_jvmti_error(jvmti,error,"Cannot Get Method name");
printf("Exception in Method: %s%s at line number: %ld\n",name,sig,location);*/
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm,char *options,void *reserved){
jvmtiEnv *jvmti;
jvmtiCapabilities capabilities;
jvmtiError error;
jint result;
jvmtiEventCallbacks callbacks;

result = (*jvm)->GetEnv(jvm,(void **)&jvmti,JVMTI_VERSION_1);
if(result!=JNI_OK){
    printf("Unable to access JVMTI! \n");
}
    gdata = (GlobalAgentData*)malloc(sizeof(GlobalAgentData));
    gdata->jvmti=jvmti;

(void)memset(&capabilities,0,sizeof(jvmtiCapabilities));
capabilities.can_tag_objects = 1;
capabilities.can_signal_thread=1;
capabilities.can_get_owned_monitor_info=1;
capabilities.can_generate_method_entry_events=1;
capabilities.can_generate_exception_events=1;
capabilities.can_tag_objects=1;
capabilities.can_access_local_variables=1;

error = (*(gdata->jvmti))->AddCapabilities(gdata->jvmti,&capabilities);
check_jvmti_error(gdata->jvmti,error,"Unable to set Capabilities");  

(void)memset(&callbacks,0,sizeof(callbacks));
callbacks.VMInit = &callbackVMInit;
callbacks.Exception = &callbackException;
//callbacks.MethodEntry = &callbackMethodEntry;

error = (*(gdata->jvmti))->SetEventCallbacks(gdata->jvmti,&callbacks,(jint)sizeof(callbacks));
check_jvmti_error(gdata->jvmti,error,"Cannot set event callbacks");

error = (*(gdata->jvmti))->SetEventNotificationMode(gdata->jvmti,JVMTI_ENABLE,JVMTI_EVENT_VM_INIT,(jthread)NULL);
check_jvmti_error(gdata->jvmti,error,"Cannot set event notification");

error = (*(gdata->jvmti))->CreateRawMonitor(gdata->jvmti,"agent data",&(gdata->lock));
check_jvmti_error(gdata->jvmti,error,"Cannot create raw monitor");

printf("A message from my custom super agent!!\n");
return JNI_OK;
}

Below is the output :

ERROR: JVMTI: 101(JVMTI_ERROR_ABSENT_INFORMATION): Cannot Get Local Variable table

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f3faab0cefa, pid=14869, tid=0x00007f3fad251700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_111-b14) (build 1.8.0_111-b14)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [liblearnAgent.so+0xefa]  callbackException+0x277
#
# Core dump written. Default location: /home/kumard/Desktop/core or core.14869
#
# An error report file with more information is saved as:
# /home/kumard/Desktop/hs_err_pid14869.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
[1]    14869 abort (core dumped)  java -agentlib:learnAgent SimpleThread

Solution

  • This is not the complete answer, but it does resolve some of the problems in your agent and prints out the values for basic data types when exception hits. We have resolved problems in the comment chat. So I'm simply posting the code here. Anyone interested in knowing more about it can see the chat. You're most welcome to give your insights on the problem too.

    #include <jni.h>
    #include <jvmti.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    typedef struct {
        jvmtiEnv *jvmti;
        jrawMonitorID lock;
    } GlobalAgentData;
    
    static GlobalAgentData *gdata;
    
    static bool 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 false;
        }
        return true;
    }
    
    static void JNICALL callbackException(jvmtiEnv *jvmti, JNIEnv *env,
            jthread thread, jmethodID method, jlocation location, jobject exception,
            jmethodID catch_method, jlocation catch_location) {
        jvmtiFrameInfo frames[10];
        jint count, entry_count_ptr;
        int i, j;
        jvmtiError error;
        char *sig, *gsig;
        jclass declaring_class_ptr;
        jvmtiLocalVariableEntry *table_ptr;
    
        error = (*jvmti)->GetStackTrace(jvmti, thread, 0, 10, frames, &count);
        if (check_jvmti_error(jvmti, error, "Cannot Get Frame") && count >= 1) {
            char *methodName, *className;
            for (i = 0; i < count; i++) {
                error = (*jvmti)->GetMethodName(jvmti, frames[i].method,
                        &methodName, &sig, &gsig);
                if (check_jvmti_error(jvmti, error, "Cannot Get method name")) {
    
                    error = (*jvmti)->GetMethodDeclaringClass(jvmti,
                            frames[i].method, &declaring_class_ptr);
                    check_jvmti_error(jvmti, error,
                            "Cannot Get method declaring class");
    
                    error = (*jvmti)->GetClassSignature(jvmti, declaring_class_ptr,
                            &className, NULL);
                    check_jvmti_error(jvmti, error, "Cannot get class signature");
    
                    error = (*jvmti)->GetLocalVariableTable(jvmti, frames[i].method,
                            &entry_count_ptr, &table_ptr);
                    check_jvmti_error(jvmti, error,
                            "Cannot Get Local Variable Table");
    
                    printf(
                            "Got Exception in  Method: %s at Line: %ld with Signature:%s,%s within Class:%s\n",
                            methodName, frames[i].location, sig, gsig, className);
    
                    if (strstr(className, "java") == NULL
                            && strstr(className, "javax") == NULL
                            && strstr(className, "sun") == NULL) {
                        for (j = 0; j < entry_count_ptr; j++) {
    
                            switch (*(table_ptr[j].signature)) {
                            case 'B': {
                                jint value_ptr;
                                error = (*jvmti)->GetLocalInt(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Byte");
    
                                printf("Value of Field %s is %d.\n", table_ptr[j].name, (jbyte)value_ptr);
                                break;
                            }
    
                            case 'C': {
                                jint value_ptr;
                                error = (*jvmti)->GetLocalInt(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Char");
    
                                printf("Value of Field %s is %c.\n", table_ptr[j].name, (jchar)value_ptr);
                                break;
                            }
                            case 'D': {
                                jdouble value_ptr;
                                error = (*jvmti)->GetLocalDouble(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Double");
    
                                printf("Value of Field %s is %f.\n", table_ptr[j].name, value_ptr);
                                break;
                            }
                            case 'F': {
                                jfloat value_ptr;
                                error = (*jvmti)->GetLocalFloat(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Float");
    
                                printf("Value of Field %s is %f.\n", table_ptr[j].name, value_ptr);
                                break;
                            }
                            case 'I': {
                                jint value_ptr;
                                error = (*jvmti)->GetLocalInt(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Integer");
    
                                printf("Value of Field %s is %d.\n", table_ptr[j].name, value_ptr);
                                break;
                            }
                            case 'J': {
                                jlong value_ptr;
                                error = (*jvmti)->GetLocalLong(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Long");
    
                                printf("Value of Field %s is %ld.\n", table_ptr[j].name, value_ptr);
                                break;
                            }
                            case 'S':{
                                jint value_ptr;
                                error = (*jvmti)->GetLocalInt(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Short");
    
                                printf("Value of Field %s is %d.\n", table_ptr[j].name, (jshort)value_ptr);
                                break;
                            }
                            case 'Z':{
                                jint value_ptr;
                                error = (*jvmti)->GetLocalInt(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Boolean");
    
                                printf("Value of Field %s is %d.\n", table_ptr[j].name, (jboolean)value_ptr);
                                break;
                            }
                            case 'L':{
                                jobject value_ptr;
                                error = (*jvmti)->GetLocalObject(jvmti, thread, i,
                                        table_ptr[j].slot, &value_ptr);
                                check_jvmti_error(jvmti, error,
                                        "Cannot Get Local Variable Object");
    
                                printf("Value of Field %s is .\n", table_ptr[j].name);
                                break;
                            }
                            default:
                                printf("Can't get %s type.\n",
                                        table_ptr[j].signature);
                            }
    
    
                            printf("Field Signature:%s\n", table_ptr[j].signature);
    
                        }
                    }
    
                }
            }
        }
    
    }
    
    JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
        jvmtiEnv *jvmti;
        jvmtiCapabilities capabilities;
        jvmtiError error;
        jint result;
        jvmtiEventCallbacks callbacks;
    
        result = (*jvm)->GetEnv(jvm, (void **) &jvmti, JVMTI_VERSION_1);
        if (result != JNI_OK) {
            printf("Unable to access JVMTI! \n");
        }
    
        gdata = (GlobalAgentData*) malloc(sizeof(GlobalAgentData));
        gdata->jvmti = jvmti;
    
        (void) memset(&capabilities, 0, sizeof(jvmtiCapabilities));
        capabilities.can_generate_exception_events = 1;
        capabilities.can_access_local_variables = 1;
    
        error = (*(gdata->jvmti))->AddCapabilities(gdata->jvmti, &capabilities);
        check_jvmti_error(gdata->jvmti, error, "Unable to set Capabilities");
    
        error = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
                JVMTI_EVENT_EXCEPTION, (jthread) NULL);
        check_jvmti_error(jvmti, error, "Cannot set Exception Event notification");
    
        (void) memset(&callbacks, 0, sizeof(callbacks));
        callbacks.Exception = &callbackException;
    
        error = (*(gdata->jvmti))->SetEventCallbacks(gdata->jvmti, &callbacks,
                (jint) sizeof(callbacks));
        check_jvmti_error(gdata->jvmti, error, "Cannot set event callbacks");
    
        error = (*(gdata->jvmti))->CreateRawMonitor(gdata->jvmti, "agent data",
                &(gdata->lock));
        check_jvmti_error(gdata->jvmti, error, "Cannot create raw monitor");
    
        printf("A message from my custom super agent!!\n");
        return JNI_OK;
    }