2

My JNI library works flawlessly on Windows, however, on Linux I always get a strange segmentation fault.

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

The stack crace from the crash file is this:

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

So it appears that it crashed when calling GetStaticObject field in the Logger class. This is that method:

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

So I'm guessing something's not right about storing the class and field IDs of these fields. But the weird thing is, I get logging output when my library starts up, even from FMOD, which the fmodDebugCallback gets called by.

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

So, logging works flawlessly on Windows, but after some time crashes on Linux. Compiled with g++ on Fedora 29 64-bit.

Update: my method for getting a 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;
}

Update 2: the code itself works up to a certain point since I'm getting log messages. Might be something to do with threads? https://hastebin.com/kuzefuwawu.txt

  • 2
    You can't store `JNIEnv*` or `jobject` or `jclass` preferences across JNI calls. There is a distressingly total lack of error checking in this code. *Every* JNI call must be error-checked, and you *must not proceed* if there was an error. – user207421 Apr 19 '19 at 23:35
  • You can store the JavaVM *jvm (pointer), jobject, and jmethodID between calls. Use `env->GetJavaVM(&jvm)` on your first JNI call with `env->NewGlobalRef(your jobject here)`. On additional calls outside JNI scope, you can use the jvm pointer to get the JNIEnv with which you can get the jclass and / or jmethodID to make your calls. This post helped me with my related issue http://adamish.com/blog/archives/327 – Paul Gregoire Dec 19 '19 at 19:54

2 Answers2

2

systemClass, errFieldId, and outFieldID are all obtained from a different JNIEnv.

The JNIEnv cannot be cached: Keeping a global reference to the JNIEnv environment

Just as it cannot be cached, you cannot store ids that were obtained from the other JNIEnv that you should no longer be using, nor should you be using anything that came from it. You need to get them all from the current valid JNIEnv.

Sean F
  • 4,344
  • 16
  • 30
  • This wasn't it sadly, I'm getting a segfault from calling FindClass now. Could my method of getting a JNIEnv be incorrect? Updated main post – Lóránt Viktor Gerber Apr 19 '19 at 22:26
  • must be something going wrong with FMODWrapper, which I know nothing about. – Sean F Apr 19 '19 at 22:30
  • It may still be something going wrong with JNIEnv. You are pulling those things out of FMODWrapper and it may very well be they are not valid for use. The JNIEnv must come from the JVM, and you have no visibility here into where they are coming from. – Sean F Apr 19 '19 at 22:32
  • Do you even know JNI_OnLoad was called yet? It's not clear that things are initialized before you use them. – Sean F Apr 19 '19 at 23:13
  • I know it's called since I'm getting logs from this exact function, it just randomly breaks at some point – Lóránt Viktor Gerber Apr 19 '19 at 23:15
  • It must be something to do with threads; I can use my getJNI() method fine from Java threads, but for some reason everything related to JNI fails from threads created by FMOD – Lóránt Viktor Gerber Apr 19 '19 at 23:28
  • The IDs are the one thing you can store.mits the `JNIEnv*` and the `jobjects` and `jclasses` that you can't store. – user207421 Apr 19 '19 at 23:36
  • If the threads created by fmod are an issue, it is the fact they are not being attached to the JVM properly, or at the right time, or in the right way, or something like that. – Sean F Apr 20 '19 at 17:44
  • @SeanF You got it right the first time. It is the `JNIEnv*` and the `jclass` and `jobject` values. It is clear from the OP's code that he is attaching threads correctly. – user207421 Apr 21 '19 at 07:58
0

The problem is not with thread affinity of class references or field IDs. The problem is with using a local class reference out of its scope. This is an implementation detail of some JVMs, that local references do not actually expire.

The fix would be to use

Logger::Logger(const char* name) {
    this->name = name;
    JNIEnv* jni = FMODWrapper::utils->getJNI();
    this->systemClass = jni->NewGlobalRef(jni->findClass("java/lang/System"));
    …
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307