Эта ошибка вызывает мой скомпилированный моддинг язык под названием grug (видео и пояснения см. здесь), чтобы время от времени аварийно завершать работу Minecraft (версия Java), когда моды вызывают SIGSEGV с бесконечной рекурсией.
Мой язык моддинга просто состоит из 9 тыс. line grug.c, предоставляющая компилятор и компоновщик для моего собственного языка программирования. Его общий объект (.so) вывод основан на примерно 300 написанных мной тестах, которые сравнивают его с ожидаемым выводом NASM. Таким образом, мой компилятор — это, по сути, компилятор NASM, который принимает в качестве входных данных файлы .grug, а не файлы сборки .s x86-64. Я объясняю это, чтобы прояснить, почему я не могу просто отказаться от NASM.
После нескольких дней тщательного сокращения исходной программы я наконец получил минимальный воспроизводимый пример, который показывает, что ошибки например FATAL ERROR в собственном методе: идентификатор статического поля, переданный в JNI, имеет вероятность быть напечатанным примерно 1 из 20 при выполнении этих условий:
- JNI используется для вызова функция C, открывающая mage.so
- mage.so был сгенерирован с помощью ld из mage.o, где mage.o был сгенерирован с помощью nasm из mage.s (где mage.s может быть просто пустым файлом)
После загрузки mage.so JNI используется для вызова функции C, которая вызывает SIGSEGV
Что странно, так это то, что создание mage.o из пустого mage.c с помощью gcc вместо использования NASM не вызывает вывод ошибок, поэтому я думаю, что виноват 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
#include
#include
#include
#include
#include
#include
#include
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 gives SIGSEGV its own stack
};
// Handle stack overflow
// See https://stackoverflow.com/a/7342398/13279557
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: пустой файл
Компиляция foo.so (вам нужно будет заменить здесь пути включения jdk на свои собственные, что ls /usr/lib/jvm< /code> может помочь):
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
Затем соберите mage.s в mage.o:
nasm mage.s -felf64
И свяжите mage.o с mage.so:
ld mage.o -o mage.so -shared
Затем выполните это пару десятков раз, пока не увидите НЕУСТРАНИМУЮ ОШИБКУ в собственном методе: печатается идентификатор статического поля, переданный в JNI:
java -Xcheck:jni -Djava.library.path=. Main.java
Вы можете использовать это во втором терминале, чтобы завершить программу, когда она зависает:
pkill -9 -f 'java -Xcheck:jni -Djava.library.path=. Main.java'
Мои вопросы
Я не понимаю, почему создание mage.o из компиляции mage.c, вместо сборки mage.s никогда не заставляет программу печатать ошибку:
gcc mage.c -c
Я вижу, что выходные данные readelf -a mage.o, objdump -D mage.o и xxd mage.o имеют вид все они значительно увеличиваются, когда mage.o создается из mage.c. Это связано с тем, что GCC сбрасывает разделы, специфичные для GNU, в файл ELF и тому подобное, поэтому я предполагаю, что ошибка, выводящаяся только при использовании NASM для сборки mage.s, может быть связана с разделами в файле ELF?< /p>
Чего я также не понимаю, так это почему проверка pthread_equal(), которую я вставил в обработчик сигнала, который сразу же завершает работу, не предотвращает ФАТАЛЬНУЮ ОШИБКУ в собственном методе: Статическое поле ID пройден в JNI от печати. Я полагал, что ошибка была вызвана входом внутреннего потока JVM в мой обработчик, тогда как он должен был войти в собственный обработчик SIGSEGV JVM, но я думаю, что нет?
Я знаю, программа выводит предупреждения о том, что она хочет, чтобы ее запускали с помощью jsig, но, как я описал в этом ответе, использование jsig невозможно, если вы хотите перезаписать обработчик SIGSEGV JVM собственным обработчиком в C (насколько я смог судить по итогам недели исследований).
Легко развести руками, обвинив в странном поведении версии NASM то, что я не использую jsig , но для меня это не имеет никакого логического смысла. Я до сих пор не уверен, действительно ли это проблема NASM или JVM.
Я использую Ubuntu 24.04.1, и вот версии программ, которые я вызываю в MRE:
$ 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 also reproduces the error
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.42
Подробнее здесь: https://stackoverflow.com/questions/793 ... or-in-nasm
Мобильная версия