Ооп поврежден при использовании в другой функции JNI - PullRequest
1 голос
/ 22 мая 2019

Вопрос в том, можем ли мы кэшировать jclass и jmethodID через различные вызовы методов JNI?

Я столкнулся с некоторым странным поведением при попытке создать объект некоторого определенного класса с кэшированными jclass и jmethodID из другого вызова метода JNI.

Вот простой пример:

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;
    }
}

Функции JNI просто создают объекты класса MyClass. Функция doSomeAction возвращает указатель, указывающий на кэшированные jclass и jmethodID. Вот реализация нативных методов:

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;
}

Проблема заключается в сбое программы при разыменовании 0 при попытке создать объект в Java_com_test_Main_doAnotherAction. Сбой при object_alloc вызове функции java_lang_Class::as_Klass(oopDesc*).

Делительно из java_lang_Class::as_Klass(oopDesc*) является

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 здесь, кажется, содержит указатель на соответствующий Oop. То, что я заметил, это первые 5 раз, когда не происходило сбоев:

rdi            0x7191eb228

ДТП

rdi            0x7191eb718

В результате чего 0x0 будет возвращено и произойдет сбой.

Что приводит к повреждению Oop при использовании jclass и jmethodID в различных JNI функциях? Если я создаю объекты с локально найденными jclass и jmethodID, все работает просто отлично.

UPD: проанализировав дамп ядра, я понял, что rdi загружается как

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

Хотя r13, похоже, не обновляется внутри моих функций JNI ...

1 Ответ

5 голосов
/ 22 мая 2019

Кэширование jclass через вызовы JNI является серьезной (, хотя типичной ) ошибкой.
jclass - это особый случай из jobject - это JNIссылка и должна управляться.

Как указано в спецификации JNI , , все объекты Java, возвращаемые функциями JNI, являются локальными ссылками .Таким образом, FindClass возвращает локальную ссылку JNI, которая становится недействительной, как только возвращается нативный метод.То есть GC не будет обновлять ссылку, если объект перемещен, или другой вызов JNI может повторно использовать тот же слот для другой ссылки JNI.

Чтобы кэшировать jclass между вызовами JNI, вы можете преобразоватьэто глобальная ссылка с использованием функции NewGlobalRef.

jthread, jstring, jarray - другие примеры jobjects, и ими также нужно управлять.

JNIEnv* также не должен кэшироваться, поскольку он действителен только в текущем потоке .

В то же время jmethodID и jfieldID могут бытьбезопасно повторно используется в вызовах JNI - они однозначно идентифицируют метод / поле в JVM и предназначены для многократного использования , пока класс держателя жив.Тем не менее, они также могут стать недействительными, если класс держателя оказывается мусором.

...