Это ошибка в JVM или в NASM?JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Это ошибка в JVM или в NASM?

Сообщение Anonymous »

Я думаю, что нашел ошибку, но не уверен, виноват ли в этом JVM, JNI, NASM, GCC или ld.
Эта ошибка вызывает мой скомпилированный моддинг язык под названием 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
Я смогли воспроизвести ошибки на моих компьютерах с Ubuntu и Arch Linux.
Что странно, так это то, что создание 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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»