Skip to content

Commit 958a759

Browse files
committed
add support for dynamically loaded JNI
1 parent 345223d commit 958a759

File tree

3 files changed

+651
-0
lines changed

3 files changed

+651
-0
lines changed

src/djni.c

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include <dlfcn.h>
2+
3+
#include "djni.h"
4+
5+
static JNI_GetDefaultJavaVMInitArgs_fnptr JNI_GetDefaultJavaVMInitArgs_fn;
6+
static JNI_CreateJavaVM_fnptr JNI_CreateJavaVM_fn;
7+
static JNI_GetCreatedJavaVMs_fnptr JNI_GetCreatedJavaVMs_fn;
8+
9+
static void *jni_dl = 0;
10+
11+
static const char *last_error = 0;
12+
13+
static int load_sym(const char *sym, void **ptr) {
14+
void *v = dlsym(jni_dl, sym);
15+
if (!v) {
16+
last_error = dlerror();
17+
dlclose(jni_dl);
18+
jni_dl = 0;
19+
return -1;
20+
}
21+
*ptr = v;
22+
return 0;
23+
}
24+
25+
/* path: path to libjvm
26+
returns: 0 = success, -1 = already loaded, -2 = dlopen error, -3 symbols not found */
27+
int djni_load(const char *path) {
28+
last_error = 0;
29+
if (jni_dl) return -1;
30+
jni_dl = dlopen(path, RTLD_LOCAL | RTLD_NOW);
31+
if (!jni_dl) {
32+
last_error = dlerror();
33+
return -2;
34+
}
35+
if (load_sym("JNI_GetDefaultJavaVMInitArgs", (void**) &JNI_GetDefaultJavaVMInitArgs_fn) ||
36+
load_sym("JNI_CreateJavaVM", (void**) &JNI_CreateJavaVM_fn) ||
37+
load_sym("JNI_GetCreatedJavaVMs", (void**) &JNI_GetCreatedJavaVMs_fn)) {
38+
JNI_GetDefaultJavaVMInitArgs_fn = 0;
39+
JNI_CreateJavaVM_fn = 0;
40+
JNI_GetCreatedJavaVMs_fn = 0;
41+
return -3;
42+
}
43+
return 0;
44+
}
45+
46+
/* returns: 0 = success, -1 = noting loaded, -2 = unload failed */
47+
int djni_unload(void) {
48+
if (jni_dl) {
49+
if (dlclose(jni_dl))
50+
return -2;
51+
jni_dl = 0;
52+
return 0;
53+
}
54+
return -1;
55+
}
56+
57+
/* The following are the "normal" JNI API calls which are routed to libjvm JNI API.
58+
If no JNI was loaded, they return -99 to distinguish it from JNI error codes. */
59+
60+
jint JNI_GetDefaultJavaVMInitArgs(void *args) {
61+
return JNI_GetDefaultJavaVMInitArgs_fn ? JNI_GetDefaultJavaVMInitArgs_fn(args) : -99;
62+
}
63+
64+
jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args) {
65+
return JNI_CreateJavaVM_fn ? JNI_CreateJavaVM_fn(pvm, penv, args) : -99;
66+
}
67+
68+
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) {
69+
return JNI_GetCreatedJavaVMs_fn ? JNI_GetCreatedJavaVMs_fn(vmBuf, bufLen, nVMs) : -99;
70+
}
71+
72+
/* define DJNI_MAIN_TEST to test the load + create + print version + destroy + unload cycle
73+
it will create an executable - just compile djni.c alone for this (not used in rJava) */
74+
#ifdef DJNI_MAIN_TEST
75+
76+
#include <stdio.h>
77+
#include <stdlib.h>
78+
#include <string.h>
79+
80+
/* calls java.lang.System.getProperty("java.version") and returns the result or NULL
81+
if something went wrong. Any non-NULL returns must be free()d */
82+
static char *java_version(JNIEnv *eenv) {
83+
char *jver = 0;
84+
jclass sys = (*eenv)->FindClass(eenv, "java/lang/System");
85+
if (sys) {
86+
jmethodID mid = (*eenv)->GetStaticMethodID(eenv, sys, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
87+
if (mid) {
88+
jstring jvs = (*eenv)->NewStringUTF(eenv, "java.version");
89+
if (jvs) {
90+
jobject jres = (*eenv)->CallStaticObjectMethod(eenv, sys, mid, jvs);
91+
(*eenv)->DeleteLocalRef(eenv, jvs);
92+
if (jres) {
93+
const char *str = (*eenv)->GetStringUTFChars(eenv, (jstring) jres, NULL);
94+
if (str) {
95+
jver = strdup(str);
96+
(*eenv)->ReleaseStringUTFChars(eenv, (jstring) jres, str);
97+
}
98+
(*eenv)->DeleteLocalRef(eenv, jres);
99+
}
100+
}
101+
}
102+
(*eenv)->DeleteLocalRef(eenv, sys);
103+
}
104+
return jver;
105+
}
106+
107+
/* create a JVM, call java_version(), destroy the JVM */
108+
static int run_jvm_test(void) {
109+
JavaVM *jvm;
110+
JNIEnv *eenv;
111+
JavaVMOption vm_options[4];
112+
JavaVMInitArgs vm_args;
113+
vm_args.version = JNI_VERSION_1_2;
114+
if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) {
115+
fprintf(stderr, "JNI 1.2 or higher is required\n");
116+
return -1;
117+
}
118+
vm_args.ignoreUnrecognized = JNI_TRUE;
119+
vm_args.options = vm_options;
120+
vm_args.nOptions = 0;
121+
jint res = JNI_CreateJavaVM(&jvm,(void **)&eenv, &vm_args);
122+
if (res != 0) {
123+
fprintf(stderr, "Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)\n", (int) res);
124+
return -1;
125+
}
126+
if (!eenv) {
127+
fprintf(stderr, "Cannot obtain JVM environment");
128+
return -1;
129+
}
130+
printf("JVM initialized, JNI version: %x\n", (*eenv)->GetVersion(eenv));
131+
{
132+
char *jver = java_version(eenv);
133+
if (jver) {
134+
printf("Java version: %s\n", jver);
135+
free(jver);
136+
}
137+
}
138+
/* this shouldn't be even needed, but was last-ditch effort - makes no difference */
139+
if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK)
140+
fprintf(stderr, "FWIW: cannot detach thread.\n");
141+
/* destroy the VM */
142+
if ((*jvm)->DestroyJavaVM(jvm) != JNI_OK) {
143+
fprintf(stderr, "ERROR: cannot destroy VM\n");
144+
return -1;
145+
}
146+
printf("JVM destroyed.\n");
147+
return 0;
148+
}
149+
150+
int test_jvm(const char *path) {
151+
int e = djni_load(path);
152+
if (e) {
153+
printf("Cannot load: %d (%s)\n", e, last_error ? last_error : "not a dl error");
154+
return 1;
155+
}
156+
printf("JVM Loaded!\n");
157+
if (!run_jvm_test()) {
158+
if (djni_unload())
159+
fprintf(stderr, "ERROR: cannot unload JNI (%s)\n", last_error ? last_error : "no information");
160+
else
161+
printf("JNI unloaded.\n");
162+
}
163+
return 0;
164+
}
165+
166+
/* for debugging on macOS - prints all dynamic loads */
167+
#ifdef __APPLE__
168+
#include <mach-o/dyld.h>
169+
170+
static void add_image(const struct mach_header* mh, intptr_t vmaddr_slide) {
171+
Dl_info DlInfo;
172+
dladdr(mh, &DlInfo);
173+
printf("LOADED: %s\n", DlInfo.dli_fname);
174+
}
175+
176+
static void rm_image(const struct mach_header* mh, intptr_t vmaddr_slide) {
177+
Dl_info DlInfo;
178+
dladdr(mh, &DlInfo);
179+
printf("REMOVE: %s\n", DlInfo.dli_fname);
180+
}
181+
#endif
182+
183+
int main(int ac, char **av) {
184+
#ifdef __APPLE__
185+
_dyld_register_func_for_add_image(add_image);
186+
_dyld_register_func_for_remove_image(rm_image);
187+
#endif
188+
189+
test_jvm("/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/lib/server/libjvm.dylib");
190+
#ifdef SAME_VM /* option 1: try the same VM twice (will load but fail to create VM) */
191+
test_jvm("/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/lib/server/libjvm.dylib");
192+
#else /* option 2: will even fail to load */
193+
test_jvm("/Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home/lib/server/libjvm.dylib");
194+
#endif
195+
return 0;
196+
}
197+
198+
#endif

src/djni.h

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* Dynamic JNI
2+
Allows to load JVM dynamically into a process.
3+
4+
(C)2024 Simon Urbanek <[email protected]>
5+
License: LGPL-2.1 or MIT
6+
*/
7+
#ifndef DJNI_H__
8+
#define DJNI_H__
9+
10+
#include "jni.h"
11+
12+
int djni_load(const char *path);
13+
int djni_unload(void);
14+
15+
jint JNI_GetDefaultJavaVMInitArgs(void *args);
16+
jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
17+
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
18+
19+
#endif

0 commit comments

Comments
 (0)