【问题标题】:JNI Linux segmentation faultJNI Linux 分段错误
【发布时间】:2019-04-19 21:35:07
【问题描述】:

我的 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

所以看起来是在 Logger 类中调用 GetStaticObject 字段时崩溃了。就是这个方法:

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

所以我猜想存储这些字段的类和字段 ID 是不对的。但奇怪的是,当我的库启动时,我会得到日志输出,甚至是来自 fmodDebugCallback 调用的 FMOD。

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 上一段时间后崩溃。在 Fedora 29 64 位上使用 g++ 编译。

更新:我获取 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

【问题讨论】:

  • 您不能跨 JNI 调用存储 JNIEnv*jobjectjclass 首选项。令人痛苦的是,这段代码完全没有错误检查。 每个 JNI 调用都必须进行错误检查,如果出现错误,您不得继续
  • 您可以在调用之间存储 JavaVM *jvm(指针)、jobject 和 jmethodID。在您的第一个 JNI 调用中使用 env-&gt;GetJavaVM(&amp;jvm)env-&gt;NewGlobalRef(your jobject here)。在 JNI 范围之外的其他调用中,您可以使用 jvm 指针来获取 JNIEnv,您可以使用它来获取 jclass 和/或 jmethodID 来进行调用。这篇文章帮助我解决了我的相关问题adamish.com/blog/archives/327

标签: java c++ linux java-native-interface jnienv


【解决方案1】:

systemClass、errFieldId、outFieldID都是从不同的JNIEnv获取的。

无法缓存 JNIEnv: Keeping a global reference to the JNIEnv environment

就像它不能被缓存一样,你不能存储从另一个 JNIEnv 获得的你不应该再使用的 id,也不应该使用来自它的任何东西。您需要从当前有效的 JNIEnv 中获取它们。

【讨论】:

  • 这不是很遗憾,我现在调用 FindClass 时遇到了段错误。我获取 JNIEnv 的方法可能不正确吗?更新主帖
  • 一定是FMODWrapper出了点问题,我对此一无所知。
  • JNIEnv 可能仍然有问题。您正在从 FMODWrapper 中提取这些东西,很可能它们无法使用。 JNIEnv 必须来自 JVM,您无法看到它们来自何处。
  • 你知道 JNI_OnLoad 被调用了吗?在你使用它们之前,不清楚事物是否已经初始化。
  • 我知道它被调用了,因为我正在从这个确切的函数中获取日志,它只是在某个时候随机中断
【解决方案2】:

问题不在于类引用或字段 ID 的线程亲和性。问题在于使用超出其范围的本地类引用。这是一些 JVM 的实现细节,本地引用实际上并没有过期。

解决方法是使用

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    • 1970-01-01
    • 1970-01-01
    • 2014-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多