Ошибка сегментации JNI Linux

Моя библиотека JNI безупречно работает в Windows, однако в Linux я всегда получаю странную ошибку сегментации.

siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000000

Стек стека из файла сбоя выглядит следующим образом:

C  [libfmodjavaL.so+0xfb8c]  JNIEnv_::GetStaticObjectField(_jclass*, _jfieldID*)+0x18
C  [libfmodjavaL.so+0xf72b]  Logger::sendToSystemOut(bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)+0x75
C  [libfmodjavaL.so+0xf7c2]  Logger::log(char const*)+0x4c
C  [libfmodjavaL.so+0xd70d]  fmodDebugCallback(unsigned int, char const*, int, char const*, char const*)+0x127

Итак, похоже, что он разбился при вызове поля GetStaticObject в классе Logger. Это тот метод:

void Logger::sendToSystemOut(bool error, std::string message) {
    JNIEnv* jni = FMODWrapper::utils->getJNI();

    jobject printStream;
    if (error) {
        printStream = jni->GetStaticObjectField(this->systemClass, this->errFieldID);
    } else {
        printStream = jni->GetStaticObjectField(this->systemClass, this->outFieldID);
    }

    jobject messageString = jni->NewStringUTF(message.c_str());
    jni->CallObjectMethod(printStream, this->printlnMethodID, messageString);
}

Итак, я предполагаю, что что-то не так с хранением идентификаторов классов и полей этих полей. Но странно то, что я получаю вывод журнала, когда моя библиотека запускается, даже из FMOD, который вызывает fmodDebugCallback.

Logger::Logger(const char* name) {
    this->name = name;

    JNIEnv* jni = FMODWrapper::utils->getJNI();

    this->systemClass = FMODWrapper::utils->findClass("java/lang/System");
    this->outFieldID = jni->GetStaticFieldID(this->systemClass, "out", "Ljava/io/PrintStream;");
    this->errFieldID = jni->GetStaticFieldID(this->systemClass, "err", "Ljava/io/PrintStream;");

    jclass printStreamClass = FMODWrapper::utils->findClass("java/io/PrintStream");
    this->printlnMethodID = jni->GetMethodID(printStreamClass, "println", "(Ljava/lang/String;)V");
}

Итак, логгирование работает безотказно в Windows, но через какое-то время вылетает в Linux. Скомпилировано с помощью g++ на 64-битной версии Fedora 29.

Обновление: мой метод получения JNIEnv*

JNIEnv* Utils::getJNI() {
    JNIEnv* jni;

    int getEnvResult = FMODWrapper::jvm->GetEnv((void**) &jni, JNI_VERSION_1_6);

    if (getEnvResult == JNI_EDETACHED) {
        FMODWrapper::jvm->AttachCurrentThread(ANDROID_VOIDPP_CAST &jni, nullptr);
    }

    return jni;
}

Обновление 2: сам код работает до определенного момента, так как я получаю сообщения журнала. Может что-то с нитками? https://hastebin.com/kuzefuwawu.txt


person Lóránt Viktor Gerber    schedule 19.04.2019    source источник
comment
Вы не можете сохранять настройки JNIEnv*, jobject или jclass для вызовов JNI. В этом коде удручающе полное отсутствие проверки ошибок. Каждый вызов JNI должен проверяться на наличие ошибок, и вы не должны продолжать в случае ошибки.   -  person user207421    schedule 20.04.2019
comment
Вы можете сохранять JavaVM *jvm (указатель), jobject и jmethodID между вызовами. Используйте env->GetJavaVM(&jvm) при первом вызове JNI с env->NewGlobalRef(your jobject here). При дополнительных вызовах вне области действия JNI вы можете использовать указатель jvm для получения JNIEnv, с помощью которого вы можете получить jclass и/или jmethodID для выполнения ваших вызовов. Этот пост помог мне с моей связанной проблемой adamish.com/blog/archives/327   -  person Paul Gregoire    schedule 19.12.2019


Ответы (2)


systemClass, errFieldId и outFieldID получены из другого JNIEnv.

JNIEnv не может быть кэширован: Сохранение глобальной ссылки на среду JNIEnv

Точно так же, как он не может быть кэширован, вы не можете хранить идентификаторы, полученные от другого JNIEnv, которые вы больше не должны использовать, и вы не должны использовать что-либо, полученное от него. Вам нужно получить их все из текущего действительного JNIEnv.

person Sean F    schedule 19.04.2019
comment
К сожалению, это не так, теперь я получаю ошибку при вызове FindClass. Может ли мой метод получения JNIEnv быть неправильным? Обновлен основной пост - person Lóránt Viktor Gerber; 20.04.2019
comment
Должно быть, что-то не так с FMODWrapper, о чем я ничего не знаю. - person Sean F; 20.04.2019
comment
С JNIEnv все еще может быть что-то не так. Вы вытаскиваете эти вещи из FMODWrapper, и вполне может быть, что они непригодны для использования. JNIEnv должен исходить от JVM, и вы не можете понять, откуда они исходят. - person Sean F; 20.04.2019
comment
Вы вообще знаете, что JNI_OnLoad уже вызывался? Не ясно, инициализируются ли вещи перед тем, как вы их используете. - person Sean F; 20.04.2019
comment
Я знаю, что это вызывается, так как я получаю журналы от этой точной функции, в какой-то момент она просто случайно ломается. - person Lóránt Viktor Gerber; 20.04.2019
comment
Это должно быть как-то связано с потоками; Я могу использовать свой метод getJNI() из потоков Java, но по какой-то причине все, что связано с JNI, не работает из потоков, созданных FMOD. - person Lóránt Viktor Gerber; 20.04.2019
comment
Идентификаторы — это единственное, что вы можете сохранить. Это JNIEnv*, jobjects и jclasses, которые вы не можете сохранить. - person user207421; 20.04.2019
comment
Если потоки, созданные fmod, являются проблемой, это факт, что они не подключены к JVM должным образом, или в нужное время, или неправильным образом, или что-то в этом роде. - person Sean F; 20.04.2019
comment
@SeanF Ты понял это с первого раза. Это значения JNIEnv*, jclass и jobject. Из кода ОП видно, что он правильно прикрепляет темы. - person user207421; 21.04.2019

Проблема не в привязке потоков к ссылкам на классы или идентификаторам полей. Проблема заключается в использовании ссылки на локальный класс вне его области. Это деталь реализации некоторых JVM, срок действия локальных ссылок на самом деле не истекает.

Исправление будет заключаться в использовании

Logger::Logger(const char* name) {
    this->name = name;
    JNIEnv* jni = FMODWrapper::utils->getJNI();
    this->systemClass = jni->NewGlobalRef(jni->findClass("java/lang/System"));
    …
person Alex Cohn    schedule 22.04.2019