Der Fehler verursacht mein kompiliertes Modding Sprache namens grug (siehe hier für Videos und Erklärungen), um Minecraft (Java-Edition) sporadisch zum Absturz zu bringen, wenn Mods ein SIGSEGV mit unendlicher Rekursion verursachen.
Meine Modding-Sprache besteht nur aus a 9k-Linie grug.c, das einen Compiler und Linker für meine benutzerdefinierte Programmiersprache liefert. Sein gemeinsames Objekt (
Code: Select all
.so
Nachdem ich tagelang mein ursprüngliches Programm sorgfältig verkleinert habe, habe ich endlich ein minimal reproduzierbares Beispiel, das diese Fehler zeigt wie FATAL ERROR in native method: Statische Feld-ID übergeben an JNI haben eine Wahrscheinlichkeit von etwa 1 zu 20, gedruckt zu werden, wenn diese Bedingungen erfüllt sind:
- JNI wird zum Aufrufen einer C-Funktion verwendet das öffnet mage.so
- wurde mit ld aus mage.o generiert, wobei mage.o mit nasm aus mage.s generiert wurde (wobei mage.s kann nur eine leere Datei sein)
Code: Select all
mage.so
Nach dem Laden von mage.so wird JNI verwendet, um eine C-Funktion aufzurufen, die ein SIGSEGV verursacht
Was seltsam ist, ist, dass mage.o aus einer leeren mage.c mit gcc generiert wird führt anstelle der Verwendung von NASM nicht dazu, dass die Fehler gedruckt werden, weshalb ich denke, dass NASM schuld sein könnte.
Minimal reproduzierbares Beispiel< /h1>
Code: Select all
Main.java
Code: Select all
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();
}
}
}
Code: Select all
foo.c
Code: Select all
#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();
}
Code: Select all
mage.s
Code: Select all
mage.c
Kompilieren von foo.so (Sie müssen die JDK-Include-Pfade hier durch Ihre eigenen ersetzen, die ls /usr/lib/jvm sind kann helfen bei):
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
Code: Select all
nasm mage.s -felf64
Code: Select all
ld mage.o -o mage.so -shared
Code: Select all
while true; do java -Xcheck:jni -XX:+AllowUserSignalHandlers -Djava.library.path=. Main.java; done
Code: Select all
Z
Meine Fragen
Was ich nicht verstehe, ist, warum das Generieren von mage.o aus dem Kompilieren von mage.c statt aus dem Zusammenstellen von mage.s das Programm nie dazu bringt, den Fehler auszugeben:< /p>
Code: Select all
gcc mage.c -c
Was ich auch nicht verstehe, ist, warum die pthread_equal()-Prüfung, die ich in den Signal-Handler eingefügt habe, der sofort beendet wird, den SCHWERWIEGENDEN FEHLER in der nativen Methode nicht verhindert: Statisches Feld ID übergeben an JNI wird nicht gedruckt. Ich habe angenommen, dass der Fehler dadurch verursacht wurde, dass ein interner JVM-Thread meinen Handler eingegeben hat, während er eigentlich den JVM-eigenen SIGSEGV-Handler eingeben sollte, aber ich vermute nicht?
Ich kenne das Das Programm gibt Warnungen darüber aus und möchte, dass es mit jsig ausgeführt wird. Wie ich jedoch in dieser Antwort beschrieben habe, ist die Verwendung von jsig nicht möglich, wenn Sie den SIGSEGV-Handler der JVM mit Ihrem eigenen Handler in C (as) überschreiben möchten Soweit ich das nach einer Woche Recherche beurteilen konnte).
Es ist leicht, die Hände hochzuwerfen, indem ich die Schuld für das seltsame Verhalten der NASM-Version darauf schiebe, dass ich nicht jsig verwende, aber es ergibt für mich keinen logischen Sinn. Ich bin mir immer noch nicht sicher, ob es sich tatsächlich um ein NASM- oder JVM-Problem handelt.
Ich verwende Ubuntu 24.04.1 und hier sind die Versionen der Programme, die ich im MRE aufrufe:
Code: Select all
$ 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