Oop gets corrupted when using in another JNI function

 ̄綄美尐妖づ 提交于 2021-02-05 08:55:28

问题


The question is can we cache jclass and jmethodID across different JNI methods invocation?

I faced some strange behavior when trying to create an object of some specific class with cached jclass and jmethodID from another JNI method invocation.

Here is a simple example:

public class Main {
    static {
        System.loadLibrary("test-crash");
    }

    public static void main(String args[]) throws InterruptedException {
        Thread.sleep(20000);
        doAnotherAction(doSomeAction());
    }

    private static native long doSomeAction();

    private static native void doAnotherAction(long ptr);
}

public class MyClass {
    public int a;

    public MyClass(int a) {
        if(a == 10){
            throw new IllegalArgumentException("a == 10");
        }
        this.a = a;
    }
}

What the JNI functions do is just creating objects of the class MyClass. The function doSomeAction return a pointer pointing to a cached jclass and jmethodID. Here is the implementation of native methods:

struct test{
    jclass mc;
    jmethodID ctor;
};

JNIEXPORT jlong JNICALL Java_com_test_Main_doSomeAction
  (JNIEnv *env, jclass jc){
  (void) jc;

  jclass mc = (*env)->FindClass(env, "com/test/MyClass");
  jmethodID ctor = (*env)->GetMethodID(env, mc, "<init>", "(I)V");

  struct test *test_ptr = malloc(sizeof *test_ptr);
  test_ptr->mc = mc;
  test_ptr->ctor = ctor;

  printf("Creating element0\n");
  jobject ae1 = (*env)->NewObject(env, test_ptr->mc, test_ptr->ctor, (jint) 0);
  (void) ae1;

  printf("Creating element0\n");
  jobject ae2 = (*env)->NewObject(env, test_ptr->mc, test_ptr->ctor, (jint) 0);
  (void) ae2;

  printf("Creating element0\n");
  jobject ae3 = (*env)->NewObject(env, test_ptr->mc, test_ptr->ctor, (jint) 0);
  (void) ae3;

  return (intptr_t) test_ptr;
}

JNIEXPORT void JNICALL Java_com_test_Main_doAnotherAction
  (JNIEnv *env, jclass jc, jlong ptr){
  (void) jc;

  struct test *test_ptr= (struct test *) ptr;
  jclass mc = test_ptr->mc;
  jmethodID ctor = test_ptr->ctor;

  printf("Creating element\n");
  jobject ae1 = (*env)->NewObject(env, mc, ctor, (jint) 0);
  (void) ae1;

  printf("Creating element\n");
  jobject ae2 = (*env)->NewObject(env, mc, ctor, (jint) 0);
  (void) ae2;

  printf("Creating element\n");
  jobject ae3 = (*env)->NewObject(env, mc, ctor, (jint) 0); //CRASH!!
  (void) ae3;
}

The problem is the program is crash when dereferencing 0 when trying to create object in the Java_com_test_Main_doAnotherAction. Crash occurs at object_alloc function calling java_lang_Class::as_Klass(oopDesc*).

Dissasmebly of java_lang_Class::as_Klass(oopDesc*) is

Dump of assembler code for function _ZN15java_lang_Class8as_KlassEP7oopDesc:                                                                                                                                       
   0x00007f7f6b02eeb0 <+0>:     movsxd rax,DWORD PTR [rip+0x932ab5]        # 0x7f7f6b96196c <_ZN15java_lang_Class13_klass_offsetE>                                                                                 
   0x00007f7f6b02eeb7 <+7>:     push   rbp                                                                                                                                                                         
   0x00007f7f6b02eeb8 <+8>:     mov    rbp,rsp                                                                                                                                                                     
   0x00007f7f6b02eebb <+11>:    pop    rbp                                                                                                                                                                         
   0x00007f7f6b02eebc <+12>:    mov    rax,QWORD PTR [rdi+rax*1]                                                                                                                                                   
   0x00007f7f6b02eec0 <+16>:    ret   

rdi here seems to contain a pointer to the relevant Oop. What I noticed is the first 5 times where no crash occurred:

rdi            0x7191eb228

The crash case is

rdi            0x7191eb718

Causing 0x0 to be returned and crash.

What does get Oop corrupted when using jclass and jmethodID across different JNI functions? If I create objects with a locally found jclass and jmethodID everything works just fine.

UPD: After analyzing core dump I figured out that the rdi is being loaded as

mov    rdi,r13
#...
mov    rdi,QWORD PTR [rdi]

While the r13 does not seem to be update inside JNI functions of mine...


回答1:


Caching jclass across JNI calls is a major (though typical) mistake.
jclass is a special case of jobject - it is a JNI reference and should be managed.

As JNI spec says, all Java objects returned by JNI functions are local references. So, FindClass returns a local JNI reference which becomes invalid as soon as the native method returns. That is, GC will not update the reference if the object is moved, or another JNI call may reuse the same slot for a different JNI reference.

In order to cache jclass across JNI calls, you may convert it to a global reference using NewGlobalRef function.

jthread, jstring, jarray are other examples of jobjects, and they should also be managed.

JNIEnv* must not be cached, too, because it is valid only in the current thread.

At the same time jmethodID and jfieldID can be safely reused across JNI calls - they unambiguously identify a method/field in the JVM and are intended for repeated use as long as the holder class is alive. However, they may also become invalid if the holder class happens to be garbage collected.



来源:https://stackoverflow.com/questions/56246852/oop-gets-corrupted-when-using-in-another-jni-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!