Skip to content

Commit

Permalink
Static-compatible init of JNA native layer
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Dario Valdespino <[email protected]>
  • Loading branch information
sgammon committed Jun 13, 2024
1 parent 5beedfc commit 5612315
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 19 deletions.
2 changes: 1 addition & 1 deletion common.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<property name="jni.revision" value="2"/>
<property name="jni.build" value="0"/> <!--${build.number}-->
<property name="jni.version" value="${jni.major}.${jni.minor}.${jni.revision}"/>
<property name="jni.md5" value="5fb98531302accd485c534c452dd952a"/>
<property name="jni.md5" value="eea709d4f4531903eb04ab51ff3840ce"/>
<property name="spec.title" value="Java Native Access (JNA)"/>
<property name="spec.vendor" value="${vendor}"/>
<property name="spec.version" value="${jna.major}"/>
Expand Down
22 changes: 21 additions & 1 deletion native/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
73 changes: 56 additions & 17 deletions native/dispatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
}

Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
17 changes: 17 additions & 0 deletions src/com/sun/jna/Native.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 5612315

Please sign in to comment.