Вопрос в том, можем ли мы кэшировать 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 ...