You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JNI加载NASM生成的.so后偶现JNI致命错误,需排查JVM/NASM问题

偶现JNI致命错误的定位疑问:JVM/NASM/GCC/ld?

我发现一个偶现Bug,会导致我开发的编译型模组语言grug在模组触发无限递归SIGSEGV时,偶尔让Minecraft Java版崩溃。grug的编译器核心是将.grug文件转换为NASM输出,依赖NASM生成共享对象(.so),因此无法弃用NASM。

经过精简,我得到最小复现场景:满足以下条件时,约1/20概率出现**"FATAL ERROR in native method: Static field ID passed to JNI"**错误:

  • 通过JNI调用C函数加载mage.so
  • mage.so由ld链接NASM汇编空文件生成的mage.o得到
  • 加载mage.so后,通过JNI调用C函数触发SIGSEGV

该问题已在Ubuntu和Arch Linux系统上复现。奇怪的是,若用GCC编译空文件mage.c生成mage.o再链接为mage.so,则不会出现错误,因此我怀疑问题可能出在NASM上。


最小复现案例

Main.java

class Main {
    private native void init();
    private native void foo();

    public static void main(String[] args) {
        new Main().run();
    }

    public void run() {
        System.loadLibrary("foo");

        init();

        long iteration = 0;
        for (int i = 0; i < 2; i++) {
            System.out.println("Iteration: " + ++iteration);
            foo();
        }
    }
}

foo.c

#include <dlfcn.h>
#include <jni.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

jmp_buf jmp_buffer;

volatile pthread_t expected_thread;

static void segv_handler(int sig) {
    (void)sig;

    {
        char msg[] = "In segv_handler()\n";
        write(STDERR_FILENO, msg, sizeof(msg)-1);
    }

    if (!pthread_equal(pthread_self(), expected_thread)) {
        char msg[] = "Unexpected thread entered handler; exiting\n";
        write(STDERR_FILENO, msg, sizeof(msg)-1);
        _exit(EXIT_FAILURE);
    }

    siglongjmp(jmp_buffer, 1);
}

JNIEXPORT void JNICALL Java_Main_init(JNIEnv *env, jobject obj) {
    (void)env;
    (void)obj;

    fprintf(stderr, "Initializing...\n");

    struct sigaction sigsegv_sa = {
        .sa_handler = segv_handler,
        .sa_flags = SA_ONSTACK, // SA_ONSTACK 为SIGSEGV分配独立栈
    };

    // 处理栈溢出
    static char stack[SIGSTKSZ];
    stack_t ss = {
        .ss_size = SIGSTKSZ,
        .ss_sp = stack,
    };

    if (sigaltstack(&ss, NULL) == -1) {
        perror("sigaltstack");
        exit(EXIT_FAILURE);
    }

    if (sigfillset(&sigsegv_sa.sa_mask) == -1) {
        perror("sigfillset");
        exit(EXIT_FAILURE);
    }

    if (sigaction(SIGSEGV, &sigsegv_sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    void *dll = dlopen("./mage.so", RTLD_NOW);
    if (!dll) {
        fprintf(stderr, "dlopen(): %s\n", dlerror());
    }
}

void recurse() {
    recurse();
}

JNIEXPORT void JNICALL Java_Main_foo(JNIEnv *env, jobject obj) {
    (void)env;
    (void)obj;

    expected_thread = pthread_self();

    if (sigsetjmp(jmp_buffer, 1)) {
        fprintf(stderr, "Jumped\n");
        return;
    }

    fprintf(stderr, "Recursing...\n");

    recurse();
}

mage.s:空文件

mage.c:空文件


编译与运行步骤

  1. 编译foo.so(需替换为本地JDK头文件路径,可通过ls /usr/lib/jvm查看):
gcc foo.c -o libfoo.so -shared -fPIC -g -Wall -Wextra -Wpedantic -Werror -Wfatal-errors -Wno-infinite-recursion -I/usr/lib/jvm/jdk-23.0.1-oracle-x64/include -I/usr/lib/jvm/jdk-23.0.1-oracle-x64/include/linux
  1. 汇编mage.s生成mage.o:
nasm mage.s -felf64
  1. 链接mage.o生成mage.so:
ld mage.o -o mage.so -shared
  1. 循环运行Main.java触发错误:
while true; do java -Xcheck:jni -XX:+AllowUserSignalHandlers -Djava.library.path=. Main.java; done
  • 按Ctrl+Z可暂停循环,使用kill %%终止进程

核心疑问

  1. 为什么用GCC编译空mage.c生成的.so不会出错?对比两种方式生成的mage.o,GCC会添加GNU特定节,是否错误与ELF文件节结构有关?
  2. 信号处理函数中加入了pthread_equal检查,非预期线程进入就直接退出,为何仍无法阻止JNI致命错误?原本以为是JVM内部线程进入自定义处理函数导致的,但现在看似乎并非如此。

补充:我知道官方提示需用jsig运行,但在自定义SIGSEGV处理函数的场景下无法使用jsig。目前仍无法确定问题根源是JVM还是NASM。


环境版本

$ java --version
java 23.0.1 2024-10-15
Java(TM) SE Runtime Environment (build 23.0.1+11-39)
Java HotSpot(TM) 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)

$ nasm --version
NASM version 2.16.01 # 2.16.03-1也能复现错误

$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.42

内容的提问来源于stack exchange,提问作者MyNameIsTrez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:04:59