Wie fange ich SIGSEGV mit sigaction() ab, wenn ich JNI verwende?Java

Java-Forum
Guest
 Wie fange ich SIGSEGV mit sigaction() ab, wenn ich JNI verwende?

Post by Guest »

Kontext
Mein Ziel ist es, SIGSEGVs aus nativem Code abzufangen, da ich verhindern möchte, dass Minecraft (Java) abstürzt, wenn ein in meiner kompilierten Modding-Sprache geschriebener Mod versehentlich eine unendliche Rekursion enthält.
Ich konnte dies für andere Programme (geschrieben in C/C++/Python) mit sigaction() und sigsetjmp() erfolgreich durchführen, aber es ist nicht so einfach in Java, wie in dieser Stack Overflow-Antwort beschrieben:

Die Java VM (zumindest Oracles Implementierung, die auch das OpenJDK enthält) verwendet POSIX-Signale für die interne Kommunikation , sodass Signalhandler installiert werden, z. für SIGSEGV. Das heißt, um korrekt zu funktionieren, muss die Java VM alle SIGSEGVs abrufen und untersuchen, die im Prozess auftreten, um zwischen „Kommunikations“-SIGSEGVs und echten SIGSEGVs zu unterscheiden, bei denen es sich um Programmfehler handeln würde.


Aber Signalhandler sind eine globale Ressource. Innerhalb eines Prozesses kann jeder native Code Signalhandler installieren und diejenigen ersetzen, die die Java VM installiert hat.
< /blockquote>

Um dieses Problem zu lösen (vom Benutzer installierte Signalhandler ersetzen die JavaVM-Signalhandler) und um Benutzercode unterzubringen, der möglicherweise einen Grund zur Installation von Signalhandlern hat, verwendet Java VM „Signalverkettung“, was im Grunde bedeutet, dass das Signal Handler (VM und Benutzer) sind hintereinander verkettet: Die JavaVM-Signalhandler werden zuerst ausgeführt; Wenn sie denken, dass das Signal für die VM uninteressant ist, gibt sie das Signal an den Benutzerhandler weiter.


The libjsig.so ist die Antwort für dieses Problem: Es ersetzt die Systemsignal-APIs (sigaction() usw.) durch seine eigenen Versionen, und jeder Benutzercode, der versucht, einen Signalhandler zu installieren, ersetzt nicht den globalen Signalhandler, sondern der Benutzerhandler angekettet hinter dem (schon installiert) Java VM-Signalhandler.

Es ist unglaublich leicht, versehentlich undefiniertes Verhalten zu haben, wenn siglongjmp()aus einem Signalhandler heraus ausgeführt wird beschrieben im Abschnitt ANWENDUNGSNUTZUNG dieser Seite, weshalb meine Modding-Sprache den Signalhandler in strategischen Momenten blockiert (mithilfe von sigprocmask) und deaktiviert. Beim folgenden minimalen reproduzierbaren Beispiel bin ich nicht so vorsichtig, da der Stack-Trace nichts mit undefiniertem Verhalten zu tun zu haben scheint.
Minimales reproduzierbares Beispiel

Code: Select all

Main.java
:

Code: Select all

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();
}
}
}

Code: Select all

foo.c
:

Code: Select all

#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 kompilieren:

Code: Select all

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 ausführen, mit libjsig.so:

Code: Select all

LD_PRELOAD=/usr/lib/jvm/jdk-23.0.1-oracle-x64/lib/server/libjsig.so java -Xcheck:jni -Djava.library.path=. Main.java
druckt Folgendes:

Code: Select all

Running in Java...
Initializing...
Recursing...
Segmentation fault (core dumped)
wobei das Ausführen von coredumpctl debug zeigt, dass der Segfault nicht von meinem SIGSEGV-Handler abgefangen wurde, da oben im Stack-Trace nur viele Aufrufe von recurse().
Wenn ich das verlasse LD_PRELOAD=/usr/lib/jvm/jdk-23.0.1-oracle-x64/lib/server/libjsig.so aus, wenn Main.java ausgeführt wird, beschwert es sich, dass ich jsig verwenden soll , was erwartet wird. Das Wichtigste dabei ist, dass dies wird ewig läuft, wie ich es mir gewünscht habe (ich habe dem Protokoll Kommentare hinzugefügt), aber natürlich auf JVMs eigenem SIGSEGV-Handler herumtrampelt, sodass es in Wirklichkeit nichts nützt:

Code: Select all

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
Ich habe diese Stack Overflow-Antwort gefunden, die erklärt, dass die JVM immer mit einem SIGABRT endet, wenn ein SIGSEGV ausgelöst wird (vorausgesetzt, jsig wird nicht verwendet und wir Ich habe einen SIGSEGV-Handler manuell installiert.
Ich kann sehen, dass SIGABRT ausgelöst wird, wenn ich diese Informationen auf mein echtes Programm anwende, aber dieses minimal reproduzierbare Beispiel ist offenbar zu gering Lassen Sie die JVM den SIGABRT erhöhen, damit ich ihn später anpassen muss.
Wenn Sie java --version ausführen, wird dies gedruckt, und ich verwende Ubuntu 24.04.1, falls es von Nutzen ist:

Code: Select all

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)

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post