Ist das ein Fehler in der JVM oder in NASM?Java

Java-Forum
Guest
 Ist das ein Fehler in der JVM oder in NASM?

Post by Guest »

Ich glaube, ich habe einen Fehler gefunden, bin mir aber nicht sicher, ob ich JVM, JNI, NASM, GCC oder LD dafür verantwortlich machen soll.
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
) Die Ausgabe basiert auf ca. 300 von mir geschriebenen Tests, die sie mit der erwarteten NASM-Ausgabe vergleichen. Mein Compiler ist also im Grunde ein NASM-Compiler, der .grug-Dateien als Eingabe verwendet und nicht .s x86-64-Assembly-Dateien. Ich erkläre dies, um zu verdeutlichen, warum ich NASM nicht einfach weglassen kann.
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
  • Code: Select all

    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)
    Nach dem Laden von mage.so wird JNI verwendet, um eine C-Funktion aufzurufen, die ein SIGSEGV verursacht
Ich konnte um die Fehler sowohl auf meinen Ubuntu- als auch auf meinen Arch-Linux-Computern zu reproduzieren.
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
: eine leere Datei

Code: Select all

mage.c
: eine leere Datei
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
Dann montieren Sie mage.s zu mage.o:

Code: Select all

nasm mage.s -felf64
Und verknüpfen Sie mage.o mit mage.so:

Code: Select all

ld mage.o -o mage.so -shared
Schließlich führen wir Main.java in einer Endlosschleife aus, die schließlich FATAL ERROR in native method: Statische Feld-ID übergeben an JNI ausgeben sollte:

Code: Select all

while true; do java -Xcheck:jni -XX:+AllowUserSignalHandlers -Djava.library.path=. Main.java; done
Strg+ drücken

Code: Select all

Z
ein paar Mal unterbricht die Schleife, wo Sie sie dann mit kill %% beenden können.
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
Ich kann sehen, dass die Ausgabe von readelf -a mage.o, objdump -D mage.o und xxd mage.o ist alles deutlich größer, wenn mage.o aus mage.c generiert wird. Dies liegt daran, dass GCC GNU-spezifische Abschnitte in der ELF-Datei usw. ablegt. Daher vermute ich, dass der Fehler, der nur ausgegeben wird, wenn NASM zum Zusammenstellen von mage.s verwendet wird, möglicherweise mit den Abschnitten in der ELF-Datei zu tun hat?< /p>
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

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post