Как мне перехватить SIGSEGV с помощью sigaction() при использовании JNI?JAVA

Программисты JAVA общаются здесь
Ответить Пред. темаСлед. тема
Anonymous
 Как мне перехватить SIGSEGV с помощью sigaction() при использовании JNI?

Сообщение Anonymous »

Контекст
Моя цель — перехватить SIGSEGV из собственного кода, поскольку я хочу предотвратить сбой Minecraft (Java) всякий раз, когда мод, написанный на моем скомпилированном языке моддинга, содержит случайную бесконечную рекурсию.
Мне удалось успешно сделать это для других программ (написанных на C/C++/Python) с помощью sigaction() и sigsetjmp(), но в Java, как описано в этом ответе на переполнение стека:

Виртуальная машина Java (по крайней мере, реализация Oracles, которая также включает OpenJDK) использует сигналы POSIX для внутренней связи, поэтому он устанавливает обработчики сигналов, например. для СИГСЕГВ. Это означает, что для правильной работы виртуальная машина Java должна получать и проверять любые SIGSEGV, возникающие в процессе, чтобы отличать «коммуникационные» SIGSEGV от реальных, которые могут быть программными ошибками.


Но обработчики сигналов — это глобальный ресурс: внутри процесса любой собственный код может устанавливать обработчики сигналов и заменять те, которые установлены виртуальной машиной Java.
< /blockquote>

Чтобы решить эту проблему (установленные пользователем обработчики сигналов заменяют обработчики сигналов JavaVM) и разместить пользовательский код, у которого может быть причина для установки обработчиков сигналов, Java VM использует «цепочку сигналов». , что по сути означает, что обработчики сигналов (VM и пользователи) связаны друг с другом: обработчики сигналов JavaVM запускаются первыми; если они считают, что сигнал не представляет интереса для виртуальной машины, она передает сигнал обработчику пользователя.


libjsig.so является ответом на эту проблему: он заменяет системные API-интерфейсы сигналов (sigaction() и т. д.) своими собственными версиями, и любой пользовательский код, пытающийся установить обработчик сигнала, не заменит глобальный обработчик сигнала, а пользовательский обработчик будет прикованный за (уже установлен) обработчик сигналов виртуальной машины Java.

Невероятно легко случайно получить неопределенное поведение при выходе siglongjmp()из обработчика сигнала, например описано в разделе «ИСПОЛЬЗОВАНИЕ ПРИЛОЖЕНИЯ» на этой странице, поэтому мой язык моддинга блокируется (с использованием sigprocmask) и отключает обработчик сигнала в стратегические моменты. В приведенном ниже минимально воспроизводимом примере я не буду так осторожен, поскольку трассировка стека, похоже, не связана с неопределенным поведением.
Минимально воспроизводимый пример
Main.java:
class Main {
private native void init();
private native void foo();

public static void main(String[] args) {
System.loadLibrary("foo");

new Main().run();
}

public void run() {
System.out.println("Running in Java...");

init();

while (true) {
foo();
}
}
}

foo.c:
#include
#include
#include
#include
#include

jmp_buf jmp_buffer;

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

siglongjmp(jmp_buffer, 1);
}

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

printf("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 recurse() {
recurse();
}

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

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

printf("Recursing...\n");

recurse();
}

Компиляция foo.so:
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

Запуск Main.java, с libjsig.so:
LD_PRELOAD=/usr/lib/jvm/jdk-23.0.1-oracle-x64/lib/server/libjsig.so java -Xcheck:jni -Djava.library.path=. Main.java

печатает это:
Running in Java...
Initializing...
Recursing...
Segmentation fault (core dumped)

где запуск отладки coredumpctl показывает, что ошибка segfault не была обнаружена моим обработчиком SIGSEGV, поскольку верхняя часть трассировки стека содержит просто множество вызовов recurse() >.
Если я оставлю LD_PRELOAD=/usr/lib/jvm/jdk-23.0.1-oracle-x64/lib/server/libjsig.so при запуске Main.java жалуется, что хочет, чтобы я использовал jsig , что и ожидается. Здесь важно то, что это действительно выполняется вечно, как я и хотел (я добавил комментарии в журнал), но это, конечно, мешает собственному обработчику SIGSEGV JVM, так что на самом деле это бесполезно:
Running in Java...
Initializing...
Recursing... # Repeated many times
Jumped # Repeated many times
Warning: SIGSEGV handler modified!
Signal Handlers:
SIGSEGV: segv_handler in libfoo.so, mask=11111111011111111101111111111110, flags=SA_ONSTACK, unblocked
*** Handler was modified!
*** Expected: Jumped
Recursing... # Repeated many times
Jumped # Repeated many times
Recursing...
javaSignalHandler in libjvm.so, mask=11100100110111111111111111111110, flags=SA_RESTART|SA_SIGINFO
SIGBUS: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGFPE: Jumped
Recursing...
javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGPIPE: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGXFSZ: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblockedJumped

Recursing...
SIGILL: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGUSR2: SR_handler in libjvm.so, mask=00000000000000000000000000000000, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGHUP: UserHandler in libjvm.so, mask=Jumped
11100100010111111101111111111110, flags=Recursing...
SA_RESTART|SA_SIGINFO, unblocked
SIGINT: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGTERM: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked
SIGQUIT: UserHandler in libjvm.soJumped
, mask=11100100010111111101111111111110Recursing...
, flags=SA_RESTART|SA_SIGINFO, blocked
SIGTRAP: SIG_DFL, mask=00000000000000000000000000000000, flags=none, unblocked
Consider using jsig library.
Jumped
Recursing...
# Runs forever

Я нашел ответ на вопрос «Переполнение стека», который объясняет, что JVM всегда завершается с помощью SIGABRT, когда поднимается SIGSEGV (при условии, что jsig не используется, и мы я вручную установил обработчик SIGSEGV).
Я вижу, как SIGABRT возникает, когда я применяю эту информацию к своей реальной программе, но этот минимальный воспроизводимый пример очевидно слишком минимален, чтобы позволить JVM поднять SIGABRT, поэтому мне нужно будет настроить его позже.
Запуск java --version выведет это, и я m на Ubuntu 24.04.1, если пригодится:
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)


Подробнее здесь: https://stackoverflow.com/questions/793 ... -using-jni
Реклама
Ответить Пред. темаСлед. тема

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

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

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

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

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • Как мне перехватить SIGSEGV с помощью sigaction() при использовании JNI?
    Anonymous » » в форуме JAVA
    0 Ответы
    17 Просмотры
    Последнее сообщение Anonymous
  • Как мне перехватить SIGSEGV с помощью sigaction() при использовании JNI?
    Anonymous » » в форуме JAVA
    0 Ответы
    15 Просмотры
    Последнее сообщение Anonymous
  • Как мне перехватить SIGSEGV с помощью sigaction() при использовании JNI?
    Anonymous » » в форуме JAVA
    0 Ответы
    9 Просмотры
    Последнее сообщение Anonymous
  • Как перехватить SIGSEGV, SIGALRM и SIGFPE с помощью sigaction() при использовании JNI?
    Anonymous » » в форуме JAVA
    0 Ответы
    11 Просмотры
    Последнее сообщение Anonymous
  • Как перехватить SIGSEGV, SIGALRM и SIGFPE с помощью sigaction() при использовании JNI?
    Anonymous » » в форуме JAVA
    0 Ответы
    8 Просмотры
    Последнее сообщение Anonymous

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