Home » Java » Old bytecode offsets after instrumentation using ASM in Java-Exceptionshub

Old bytecode offsets after instrumentation using ASM in Java-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I want to print the bytecode offset where the exception happened at runtime. I have the JVMTI patch to do it. But when I instrument the code using ASM the printed bytecode offset for the exception is different because of the instrumentation. Is there a way to get the old bytecode offset of the instruction after the instrumentation is done? Or is there any alternative way to do it?

Here is an example class:

class Test {
  public static void main(String args[]) {
    System.out.println("Before exception!!!");
    System.out.println(5/0);
    System.out.println("After exception!!!");
  }
}

The JVMTI Patch is as follows:

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

static jclass Exception;
static jfieldID detailMessage;

void JNICALL VMInit(jvmtiEnv* jvmti, JNIEnv* env, jthread thread) {
    jclass localE = env->FindClass("java/lang/Exception");
    Exception = (jclass) env->NewGlobalRef(localE);

    jclass Throwable = env->FindClass("java/lang/Throwable");
    detailMessage = env->GetFieldID(Throwable, "detailMessage", "Ljava/lang/String;");
}

void JNICALL ExceptionCallback(jvmtiEnv* jvmti, JNIEnv* env, jthread thread,
                               jmethodID method, jlocation location, jobject exception,
                               jmethodID catch_method, jlocation catch_location) {
    if (env->IsInstanceOf(exception, Exception)) {
        char buf[32];
        sprintf(buf, "location=%ld", (long)location);
        env->SetObjectField(exception, detailMessage, env->NewStringUTF(buf));
    }
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
    jvmtiEnv* jvmti;
    vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0);

    jvmtiCapabilities capabilities = {0};
    capabilities.can_generate_exception_events = 1;
    jvmti->AddCapabilities(&capabilities);

    jvmtiEventCallbacks callbacks = {0};
    callbacks.VMInit = VMInit;
    callbacks.Exception = ExceptionCallback;
    jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, NULL);

    return 0;
}

To compile the patch use the command:

g++ -shared -fPIC -I/path/to/java/jdk1.7.0_80/include -I/path/to/java/jdk1.7.0_80/include/linux -o libJVMTIPatch.so JVMTIPatch.cpp

To run use the ‘-agentpath’ option:

java -agentpath:/path/to/libJVMTIPatch.so Test

The output will be:

Before exception!!!
Exception in thread "main" java.lang.ArithmeticException: location=13
at Test.main(Test.java:6)

Where “location=13” is the bytecode offset where the exception happened.

class Test {
  Test();
  Code:
   0: aload_0
   1: invokespecial #1                  // Method java/lang/Object."<init>":()V
   4: return

public static void main(java.lang.String[]);
Code:
   0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: ldc           #3                  // String Before exception!!!
   5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  11: iconst_5
  12: iconst_0
  13: idiv
  14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
  17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  20: ldc           #6                  // String After exception!!!
  22: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  25: return
}

But when the class Test is instrumented for some purpose by ASM the bytecode offset of idiv instruction is changed and hence the output is different as expected:

public class Test {
Test();
Code:
   0: invokestatic  #39                 // Method rr/state/ShadowThread.getCurrentShadowThread:()Lrr/state/ShadowThread;
   3: astore_1
   4: aload_1
   5: astore_1
   6: ldc           #41                 // String Test.<init>()V 0
   8: aload_1
   9: invokestatic  #47                 // Method rr/tool/RREventGenerator.bbEnter:(Ljava/lang/String;Lrr/state/ShadowThread;)V
  12: aload_0
  13: invokespecial #1                  // Method java/lang/Object."<init>":()V
  16: aload_0
  17: ldc           #49                 // String ALOAD 0
  19: aload_1
  20: invokestatic  #53                 // Method rr/tool/RREventGenerator.objIdPrint:(Ljava/lang/Object;Ljava/lang/String;Lrr/state/ShadowThread;)V
  23: return

public static void main(java.lang.String[]);
Code:
   0: invokestatic  #39                 // Method rr/state/ShadowThread.getCurrentShadowThread:()Lrr/state/ShadowThread;
   3: astore_1
   4: aload_1
   5: astore_1
   6: ldc           #55                 // String Test.main([Ljava/lang/String;)V 0
   8: aload_1
   9: invokestatic  #47                 // Method rr/tool/RREventGenerator.bbEnter:(Ljava/lang/String;Lrr/state/ShadowThread;)V
  12: aload_0
  13: ldc           #49                 // String ALOAD 0
  15: aload_1
  16: invokestatic  #53                 // Method rr/tool/RREventGenerator.objIdPrint:(Ljava/lang/Object;Ljava/lang/String;Lrr/state/ShadowThread;)V
  19: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  22: ldc           #56                 // int 0
  24: invokestatic  #61                 // Method rr/instrument/classes/ClassInitNotifier.__$rr_static_access:(I)V
  27: ldc           #3                  // String Before exception!!!
  29: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  32: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  35: ldc           #56                 // int 0
  37: invokestatic  #61                 // Method rr/instrument/classes/ClassInitNotifier.__$rr_static_access:(I)V
  40: iconst_5
  41: iconst_0
  42: idiv
  43: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
  46: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  49: ldc           #56                 // int 0
  51: invokestatic  #61                 // Method rr/instrument/classes/ClassInitNotifier.__$rr_static_access:(I)V
  54: ldc           #6                  // String After exception!!!
  56: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  59: return
}

Hence the output becomes:

Before exception!!!
Exception in thread "main" java.lang.ArithmeticException: location=42
at Test.main(Test.java:6)

I want to get the offset before the instrumentation was done.

How to&Answers: