Эта ошибка вызывает мой скомпилированный моддинг язык под названием 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
Наконец, мы запускаем Main.java в бесконечном цикле, который в конечном итоге должен вывести НЕУСТРАНИМУЮ ОШИБКУ в собственном методе: идентификатор статического поля передается в JNI:
while true; do java -Xcheck:jni -Djava.library.path=. Main.java; done
Нажатие Ctrl+Z несколько раз приостанавливает цикл, после чего вы можете использовать команду kill %%, чтобы завершить его.
Мои вопросы
Чего я не понимаю, так это того, почему при создании mage.o из компиляции mage.c вместо сборки mage.s программа никогда не печатает ошибку:< /p>
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
Мобильная версия