问题
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
回答1:
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.
回答2:
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"));
…
来源:https://stackoverflow.com/questions/55768112/jni-linux-segmentation-fault