5

I'm writing an instrumentation tool for Java applications using JVMTI. I've seen that JVMTI detects when an exception has been thrown and when has been caught according to http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html#Exception. This document states for both events Exception and ExceptionCatch

The exception field identifies the thrown exception object.

although it does not indicate on how to compare them during the run (i.e. to compare an exception provided in Exception corresponds to the expcetion caught in ExceptionCatch). In other words, for

# java -version
java version "1.7.0_85"
OpenJDK Runtime Environment (IcedTea 2.6.1) (7u85-2.6.1-5ubuntu0.14.04.1)
OpenJDK 64-Bit Server VM (build 24.85-b03, mixed mode)

it does not seem to be always true when comparing the jexception/jobject directly. Consider the JVMTI agent (source code below) that monitors the Exception and ExceptionCatch events, also consider the subsequent Java naïve example (source-code also included) that throws an Exception, and finally you can run the example with the agent through "make run" with the given Makefile (included at the very end).

If I run the example using OpenJDK 7 it gives me the followign results:

# make run
...
cb_Exception (exception=0x2ae6b8087be8)
cb_ExceptionCatch (exception=0x2ae6b80859f8 vs last_exception=0x2ae6b8087be8)
AreSameObject? = 0
cb_Exception (exception=0x2ae6b80859f8)
cb_ExceptionCatch (exception=0x2ae6b807a388 vs last_exception=0x2ae6b80859f8)
AreSameObject? = 0
cb_Exception (exception=0x2ae6b807a388)
cb_ExceptionCatch (exception=0x2ae6b807a388 vs last_exception=0x2ae6b807a388)
AreSameObject? = 1
cb_Exception (exception=0x2ae6b807a388)
cb_ExceptionCatch (exception=0x2ae6b8078108 vs last_exception=0x2ae6b807a388)
AreSameObject? = 0
cb_Exception (exception=0x2ae6b8078108)
cb_ExceptionCatch (exception=0x2ae6b8078108 vs last_exception=0x2ae6b8078108)
AreSameObject? = 1
cb_Exception (exception=0x2ae6b8078108)
cb_ExceptionCatch (exception=0x2ae6b8078108 vs last_exception=0x2ae6b8078108)
AreSameObject? = 1
before doing work
cb_Exception (exception=0x2ae6b8078108)
cb_ExceptionCatch (exception=0x2ae6b8078108 vs last_exception=0x2ae6b8078108)
AreSameObject? = 1
after doing work

If I run the example using IBM's Java 1.7.0 the situation is somewhat similar.

# make run
...
cb_Exception (exception=0x7d78a0)
cb_ExceptionCatch (exception=0x7d7950 vs last_exception=0x7d78a0)
AreSameObject? = 1
cb_Exception (exception=0x7d7938)
cb_ExceptionCatch (exception=0x7d7950 vs last_exception=0x7d7938)
AreSameObject? = 0
cb_Exception (exception=0x7d7938)
cb_ExceptionCatch (exception=0x7d79a8 vs last_exception=0x7d7938)
AreSameObject? = 1
before doing work
cb_Exception (exception=0x7d7a60)
cb_ExceptionCatch (exception=0x7d7a98 vs last_exception=0x7d7a60)
AreSameObject? = 0
after doing work

In OpenJDK's, notice that each Exception has a corresponding ExceptionCatch, yet there are six Exceptions outside the main Java code which I suspect that come from the Java VM itself. Notice that the value for exception is not the same in every pair. After th 5th callback call, the value for exception seems to get constant. The situation is somewhat similar in IBM's Java but with less exceptions raised. As a suggestion from Chen Harel from the comments, I've stored the raised exception and compared to the caught exception through the IsSameObject JNI's method but JNI claims that the exceptions are not the same.

So, can exceptions be identified during the application life-time by the Exception and ExceptionCatch? If so, which is the appropriate way to compare exceptions through JVMTI or JNI?

agent.c

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

#include <jni.h>
#include <jvmti.h>

#define CHECK_JVMTI_ERROR(x,call) \
    { if (x != JVMTI_ERROR_NONE) { fprintf (stderr, "Error during %s in %s:%d\n", #call, __FILE__, __LINE__); } }

/* Global static data */
static jvmtiEnv     *jvmti;
static jrawMonitorID ExtraeJ_AgentLock;

jobject last_exception;

static void JNICALL cb_Exception (jvmtiEnv *jvmti_env, JNIEnv* jni_env,
    jthread thread, jmethodID method, jlocation location, jobject exception,
    jmethodID catch_method, jlocation catch_location)
{
    printf ("cb_Exception (exception=%p)\n", exception);

    last_exception = exception;
}

static void JNICALL cb_ExceptionCatch (jvmtiEnv *jvmti_env,
    JNIEnv* jni_env, jthread thread, jmethodID method, jlocation location,
    jobject exception)
{
    printf ("cb_ExceptionCatch (exception=%p vs last_exception=%p)\n"
      "AreSameObject? = %d\n",
      exception,
      last_exception,
      (*jni_env)->IsSameObject(jni_env, exception, last_exception));
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
    jint                rc;
    jvmtiError          r;
    jvmtiCapabilities   capabilities;
    jvmtiEventCallbacks callbacks;

    /* Get JVMTI environment */
    rc = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION);
    if (rc != JNI_OK)
    {
        fprintf (stderr, "Error!: Unable to create jvmtiEnv, rc=%d\n", rc);
        return -1;
    }

    /* Get/Add JVMTI capabilities */
    memset(&capabilities, 0, sizeof(capabilities));
    capabilities.can_generate_exception_events = 1;
    r = (*jvmti)->AddCapabilities(jvmti, &capabilities);
    CHECK_JVMTI_ERROR(r, AddCapabilities);

    /* Set callbacks and enable event notifications */
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.Exception               = &cb_Exception;
    callbacks.ExceptionCatch          = &cb_ExceptionCatch;
    r = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
    CHECK_JVMTI_ERROR(r, SetEventCallbacks);

    /* Exception events */
    r = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 
      JVMTI_EVENT_EXCEPTION, NULL);
    CHECK_JVMTI_ERROR(r, SetEventNotificationMode);
    r = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 
      JVMTI_EVENT_EXCEPTION_CATCH, NULL);
    CHECK_JVMTI_ERROR(r, SetEventNotificationMode);

    return 0;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
}

example.java

public class example
{
    void except () throws Exception
    { throw new Exception ("new-exception"); }

    void do_work()
    {
        System.out.println ("before doing work");

        try
        { except(); }
        catch (Exception e)
        {}

        System.out.println ("after doing work");
    }

    public static void main (String [] args)
    {
        example e = new example();
        e.do_work ();
    }
}

Makefile

JAVA_JDK=/usr/lib/jvm/java-7-openjdk-amd64

all: libagent.so example.class

libagent.so: agent.c
    gcc -shared -fPIC -DPIC agent.c -o libagent.so -I$(JAVA_JDK)/include

example.class: example.java
    javac example.java

run: libagent.so example.class
    java -agentpath:$(PWD)/libagent.so example

UPDATE 9th, Nov

I have created a global reference to the exception as Chen Harel suggested. The modified version of the agent.c is as follows and the output of the execution is shown below.

agent.c

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

#include <jni.h>
#include <jvmti.h>

#define CHECK_JVMTI_ERROR(x,call) \
    { if (x != JVMTI_ERROR_NONE) { fprintf (stderr, "Error during %s in %s:%d\n", #call, __FILE__, __LINE__); } }

/* Global static data */
static jvmtiEnv     *jvmti;
static jrawMonitorID ExtraeJ_AgentLock;

jobject last_exception;

static void JNICALL cb_Exception (jvmtiEnv *jvmti_env, JNIEnv* jni_env,
    jthread thread, jmethodID method, jlocation location, jobject exception,
    jmethodID catch_method, jlocation catch_location)
{
    printf ("cb_Exception (exception=%p)\n", exception);

    last_exception = (*jni_env)->NewGlobalRef (jni_env, exception);
}

static void JNICALL cb_ExceptionCatch (jvmtiEnv *jvmti_env,
    JNIEnv* jni_env, jthread thread, jmethodID method, jlocation location,
    jobject exception)
{
    printf ("cb_ExceptionCatch (exception=%p vs last_exception=%p)\n"
      "AreSameObject? = %d\n",
      exception,
      last_exception,
      (*jni_env)->IsSameObject(jni_env, exception, last_exception));

    (*jni_env)->DeleteGlobalRef(jni_env, last_exception);
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
    jint                rc;
    jvmtiError          r;
    jvmtiCapabilities   capabilities;
    jvmtiEventCallbacks callbacks;

    /* Get JVMTI environment */
    rc = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION);
    if (rc != JNI_OK)
    {
        fprintf (stderr, "Error!: Unable to create jvmtiEnv, rc=%d\n", rc);
        return -1;
    }

    /* Get/Add JVMTI capabilities */
    memset(&capabilities, 0, sizeof(capabilities));
    capabilities.can_generate_exception_events = 1;
    r = (*jvmti)->AddCapabilities(jvmti, &capabilities);
    CHECK_JVMTI_ERROR(r, AddCapabilities);

    /* Set callbacks and enable event notifications */
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.Exception               = &cb_Exception;
    callbacks.ExceptionCatch          = &cb_ExceptionCatch;
    r = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
    CHECK_JVMTI_ERROR(r, SetEventCallbacks);

    /* Exception events */
    r = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 
      JVMTI_EVENT_EXCEPTION, NULL);
    CHECK_JVMTI_ERROR(r, SetEventNotificationMode);
    r = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, 
      JVMTI_EVENT_EXCEPTION_CATCH, NULL);
    CHECK_JVMTI_ERROR(r, SetEventNotificationMode);

    return 0;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
}

** Output of make run **

# make run
...
cb_Exception (exception=0x2b13b0087c18)
cb_ExceptionCatch (exception=0x2b13b0085a08 vs last_exception=0x2b13e4001608)
AreSameObject? = 0
cb_Exception (exception=0x2b13b0085a08)
cb_ExceptionCatch (exception=0x2b13b007a388 vs last_exception=0x2b13e4001610)
AreSameObject? = 0
cb_Exception (exception=0x2b13b007a388)
cb_ExceptionCatch (exception=0x2b13b007a388 vs last_exception=0x2b13e4001618)
AreSameObject? = 1
cb_Exception (exception=0x2b13b007a388)
cb_ExceptionCatch (exception=0x2b13b0078108 vs last_exception=0x2b13b0085a00)
AreSameObject? = 0
cb_Exception (exception=0x2b13b0078108)
cb_ExceptionCatch (exception=0x2b13b0078108 vs last_exception=0x2b13b0085a08)
AreSameObject? = 1
cb_Exception (exception=0x2b13b0078108)
cb_ExceptionCatch (exception=0x2b13b0078108 vs last_exception=0x2b13b0085a10)
AreSameObject? = 1
before doing work
cb_Exception (exception=0x2b13b0078108)
cb_ExceptionCatch (exception=0x2b13b0078108 vs last_exception=0x2b13b0085a18)
AreSameObject? = 1
after doing work
Harald
  • 3,110
  • 1
  • 24
  • 35
  • Hi Harald, i am trying to write an agent similar to yours, but unfortunately i am getting exceptions in findClass and loadClass, i understand that they might be caused due to NoClassDefFoundException, but i am running the java program from the same directory which contains the class file, any idea on this ? – kumarD Feb 10 '17 at 12:37

1 Answers1

1

jobject is a C++ pointer and the reference to the heap is handled beneath it. So this is more of a pointer to a pointer if you think about it.

The way to test if two jobjects are the same is to use the jni method IsSameObject

If you want to test the type of the exception, use GetObjectClass + IsInstanceOf

EDIT

Note that in order to keep jobject(s) valid between methods you will have to create a reference for them with jni NewGlobalRef method.

Chen Harel
  • 9,684
  • 5
  • 44
  • 58
  • Chen, I have updated my example storing the thrown exception into a global variable and then comparing the caught exception with this global variable. However, the results do not seem satisfactory because exceptions do not match. Additionally, I have tested this on IBM's Java 1.7.0 and results are somewhat similar. – Harald Nov 05 '15 at 08:45
  • edit answer with brevity on jni NewGlobalRef to create reusable jobject(s) – Chen Harel Nov 05 '15 at 22:06
  • Interestingly, the usage of NewGlobalRef worked in IBM's Java 1.7.0. However, the situation does not improve in OpenJDK 7, there are several exceptions that do not match. – Harald Nov 06 '15 at 10:05
  • I have updated the agent.c with the creation (and deletion) of the Global Reference. I've also posted the result of the execution. – Harald Nov 09 '15 at 09:20