From 561231575a78b83f7735787b8ae6201d22245bd0 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Wed, 12 Jun 2024 20:32:30 -0700 Subject: [PATCH] Static-compatible init of JNA native layer When operating under static linkage in SVM (Native Image), JNA's `JNI_OnLoad` hooks are not run. We need to sanity-check at the first JNI border and run static initialization manually. Additionally, `JNI_OnLoad` should be provided in static contexts as `JNI_OnLoad_jnidispatch`. This changeset fixes both issues. Signed-off-by: Sam Gammon Signed-off-by: Dario Valdespino --- common.xml | 2 +- native/Makefile | 22 ++++++++++- native/dispatch.c | 73 ++++++++++++++++++++++++++++--------- src/com/sun/jna/Native.java | 17 +++++++++ 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/common.xml b/common.xml index 7e26ebcc0..bc1c63b32 100644 --- a/common.xml +++ b/common.xml @@ -27,7 +27,7 @@ - + diff --git a/native/Makefile b/native/Makefile index 12f72264e..5695d6730 100644 --- a/native/Makefile +++ b/native/Makefile @@ -62,6 +62,7 @@ JAVA_INCLUDES=-I"$(JAVA_HOME)/include" \ BUILD=../build/native JAVAH=$(BUILD) INSTALLDIR=../build/$(OS) +JNA_STATIC=no JNIDISPATCH_OBJS=$(BUILD)/dispatch.o $(BUILD)/callback.o $(EXTRAOBJS) RSRC=$(BUILD)/rsrc.o DLLCB=$(BUILD)/dll-callback.o @@ -342,6 +343,10 @@ ifeq ($(ARCH),ppc) endif endif +ifeq ($(JNA_STATIC),yes) +COPT+=-DJNA_STATIC=1 +endif + # CC_OPTS only applied to objects build for jnidispatch, not for libffi # -Wno-unknown-warning-option # => Suppress warning for unknown warnings @@ -497,8 +502,23 @@ $(LIBRARY): $(JNIDISPATCH_OBJS) $(FFI_LIB) $(LD) $(LDFLAGS) $(JNIDISPATCH_OBJS) $(FFI_LIB) $(LIBS) $(STRIP) $@ -$(LIBRARY_STATIC): $(JNIDISPATCH_OBJS) $(FFI_LIB) +static-objs: $(FFI_LIB) $(JNIDISPATCH_OBJS) + @# the rebuild enters here + +$(LIBRARY_STATIC): $(LIBRARY) $(JNIDISPATCH_OBJS) $(FFI_LIB) + @# we force a rebuild of the JNI dispatch objects, but in static mode. this is safe + @# because they are cleaned afterward, and `$(LIBRARY)` is guaranteed to have finished + @# by now, along with `$(JNIDISPATCH_OBJS)`. we replace the objects again once we are + @# finished, to avoid poisoning the shared library on the next build. + @# + @# this looks like it may be trickery, but it really isn't. see `dispatch.c` for how the + @# macro is used; it causes `JNI_OnLoad` to be exported as `JNI_OnLoad_jnidispatch` etc + @# instead, so that it can safely be loaded for static JNI. + rm -f $(JNIDISPATCH_OBJS) + $(MAKE) JNA_STATIC=yes static-objs $(AR) rcs $@ $(JNIDISPATCH_OBJS) + rm -f $(JNIDISPATCH_OBJS) + $(MAKE) STATIC=no static-objs $(TESTLIB): $(BUILD)/testlib.o $(LD) $(LDFLAGS) $< $(LIBS) diff --git a/native/dispatch.c b/native/dispatch.c index 8a03a643b..29f944944 100644 --- a/native/dispatch.c +++ b/native/dispatch.c @@ -3314,6 +3314,20 @@ is_protected() { return JNI_FALSE; } +jint initializeJnaStatics(JNIEnv *env) { + int result = JNI_VERSION_1_4; + const char* err; + if ((err = JNA_init(env)) != NULL) { + fprintf(stderr, "JNA: Problems loading core IDs: %s\n", err); + result = 0; + } + else if ((err = JNA_callback_init(env)) != NULL) { + fprintf(stderr, "JNA: Problems loading callback IDs: %s\n", err); + result = 0; + } + return result; +} + JNIEXPORT jboolean JNICALL Java_com_sun_jna_Native_isProtected(JNIEnv *UNUSED(env), jclass UNUSED(classp)) { return is_protected(); @@ -3335,6 +3349,10 @@ Java_com_sun_jna_Native_getNativeVersion(JNIEnv *env, jclass UNUSED(classp)) { #ifndef JNA_JNI_VERSION #define JNA_JNI_VERSION "undefined" #endif + // sanity check initialization + if (classString == NULL) { + initializeJnaStatics(env); + } return newJavaString(env, JNA_JNI_VERSION, CHARSET_UTF8); } @@ -3346,39 +3364,50 @@ Java_com_sun_jna_Native_getAPIChecksum(JNIEnv *env, jclass UNUSED(classp)) { return newJavaString(env, CHECKSUM, CHARSET_UTF8); } -JNIEXPORT jint JNICALL -JNI_OnLoad(JavaVM *jvm, void *UNUSED(reserved)) { +JNIEXPORT jboolean JNICALL Java_com_sun_jna_Native_isStaticEnabled(JNIEnv * UNUSED(env), jclass UNUSED(classp)) { +#ifdef JNA_STATIC + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + +JNIEXPORT jint JNICALL Java_com_sun_jna_Native_initializeStatic(JNIEnv *env, jclass UNUSED(classp)) { + return initializeJnaStatics(env); +} + +jint setupJna(JavaVM *jvm) { JNIEnv* env; - int result = JNI_VERSION_1_4; int attached = (*jvm)->GetEnv(jvm, (void *)&env, JNI_VERSION_1_4) == JNI_OK; - const char* err; - if (!attached) { if ((*jvm)->AttachCurrentThread(jvm, (void *)&env, NULL) != JNI_OK) { fprintf(stderr, "JNA: Can't attach native thread to VM on load\n"); return 0; } } - - if ((err = JNA_init(env)) != NULL) { - fprintf(stderr, "JNA: Problems loading core IDs: %s\n", err); - result = 0; - } - else if ((err = JNA_callback_init(env)) != NULL) { - fprintf(stderr, "JNA: Problems loading callback IDs: %s\n", err); - result = 0; - } + int result = initializeJnaStatics(env); if (!attached) { if ((*jvm)->DetachCurrentThread(jvm) != 0) { fprintf(stderr, "JNA: could not detach thread on initial load\n"); } } - return result; } -JNIEXPORT void JNICALL -JNI_OnUnload(JavaVM *vm, void *UNUSED(reserved)) { +#ifdef JNA_STATIC +JNIEXPORT jint JNICALL +JNI_OnLoad_jnidispatch(JavaVM *jvm, void *UNUSED(reserved)) { + setupJna(jvm); + return JNI_VERSION_1_8; // upgrade to JNI_VERSION_1_8; required for static JNI +} +#else +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *jvm, void *UNUSED(reserved)) { + return setupJna(jvm); +} +#endif + +void unloadJna(JavaVM *vm) { jobject* refs[] = { &classObject, &classClass, &classMethod, &classString, @@ -3443,6 +3472,16 @@ JNI_OnUnload(JavaVM *vm, void *UNUSED(reserved)) { } } +#ifdef JNA_STATIC +JNIEXPORT void JNICALL JNI_OnUnload_jnidispatch(JavaVM *jvm, void *UNUSED(reserved)) { + unloadJna(jvm); +} +#else +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *UNUSED(reserved)) { + unloadJna(jvm); +} +#endif + JNIEXPORT void JNICALL Java_com_sun_jna_Native_unregister(JNIEnv *env, jclass UNUSED(ncls), jclass cls, jlongArray handles) { jlong* data = (*env)->GetLongArrayElements(env, handles, NULL); diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index 7edeb05c5..66624a13d 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.lang.System; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; @@ -947,6 +948,19 @@ private static void loadNativeDispatchLibrary() { } String libName = System.getProperty("jna.boot.library.name", "jnidispatch"); + + // if in static init mode, load the library by name and force-run the initializer + if (!Boolean.getBoolean("jna.skipStatic")) { + try { + System.loadLibrary(libName); + if (isStaticEnabled()) { + assert initializeStatic() == 0x00010008; + } + return; + } catch (UnsatisfiedLinkError err) { + // continue + } + } String bootPath = System.getProperty("jna.boot.library.path"); if (bootPath != null) { // String.split not available in 1.4 @@ -1198,6 +1212,8 @@ else if (!Boolean.getBoolean("jna.nounpack")) { **/ private static native int sizeof(int type); + private static synchronized native boolean isStaticEnabled(); + private static synchronized native int initializeStatic(); private static native String getNativeVersion(); private static native String getAPIChecksum(); @@ -2020,6 +2036,7 @@ public static void main(String[] args) { ? pkg.getImplementationVersion() : DEFAULT_BUILD; if (version == null) version = DEFAULT_BUILD; System.out.println("Version: " + version); + System.out.println(" Static: " + isStaticEnabled()); System.out.println(" Native: " + getNativeVersion() + " (" + getAPIChecksum() + ")"); System.out.println(" Prefix: " + Platform.RESOURCE_PREFIX);