Skip to content

Commit 894caa7

Browse files
committed
feat: add completely automated JNI method registration
1 parent 73927d9 commit 894caa7

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

core-syscall/src/main/java/dev/tmpfs/libcoresyscall/elfloader/NativeRegistrationHelper.java

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import android.text.TextUtils;
44

55
import androidx.annotation.NonNull;
6+
import androidx.annotation.Nullable;
67

78
import java.lang.reflect.Method;
89
import java.lang.reflect.Modifier;
10+
import java.nio.ByteBuffer;
911
import java.util.ArrayList;
12+
import java.util.HashSet;
13+
import java.util.Map;
14+
import java.util.Objects;
1015

1116
import dev.tmpfs.libcoresyscall.core.NativeAccess;
1217

@@ -23,6 +28,16 @@ public static class RegistrationSummary {
2328
public final ArrayList<Method> missedMethods = new ArrayList<>();
2429
// The methods that were already registered.
2530
public final ArrayList<Method> skippedMethods = new ArrayList<>();
31+
32+
@NonNull
33+
@Override
34+
public String toString() {
35+
return "RegistrationSummary{" +
36+
"registeredMethods=" + registeredMethods +
37+
", missedMethods=" + missedMethods +
38+
", skippedMethods=" + skippedMethods +
39+
'}';
40+
}
2641
}
2742

2843
public interface NativeLibrarySymbolResolver {
@@ -186,4 +201,131 @@ private static String mangleForJni(@NonNull String s) {
186201
return result.toString();
187202
}
188203

204+
/**
205+
* See {@link #registerNativeMethodsForLibrary(long, byte[], ClassLoader)}, where the current class loader is used.
206+
*/
207+
public static RegistrationSummary registerNativeMethodsForLibrary(long handle, @NonNull byte[] elfData) {
208+
ClassLoader currentClassLoader = NativeRegistrationHelper.class.getClassLoader();
209+
assert currentClassLoader != null;
210+
return registerNativeMethodsForLibrary(handle, elfData, currentClassLoader);
211+
}
212+
213+
/**
214+
* Gets the class name for the given JNI symbol name.
215+
*
216+
* @param symbolName the JNI symbol name, e.g. "Java_com_example_Foo_bar" or "Java_com_example_Foo_bar__I"
217+
* @return the class name, e.g. "com.example.Foo", or null if the symbol name is not a valid JNI symbol name
218+
*/
219+
@Nullable
220+
public static String getClassNameForJniSymbolName(@NonNull String symbolName) {
221+
if (!symbolName.startsWith("Java_")) {
222+
return null;
223+
}
224+
char[] chars = symbolName.toCharArray();
225+
StringBuilder sb = new StringBuilder();
226+
int i = "Java_".length();
227+
try {
228+
while (i < chars.length) {
229+
char ch = chars[i];
230+
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) {
231+
// normal character
232+
sb.append(ch);
233+
i++;
234+
} else if (ch == '_') {
235+
// peek next char
236+
char second = chars[i + 1];
237+
if (second == '1') {
238+
// '_1' -> '_'
239+
sb.append('_');
240+
i += 2;
241+
} else if (second == '2') {
242+
// '_2' -> ';'
243+
sb.append(';');
244+
i += 2;
245+
} else if (second == '3') {
246+
// '_3' -> '['
247+
sb.append('[');
248+
i += 2;
249+
} else if (second == '0') {
250+
// '_0xxxx' -> unicode character
251+
int code = Integer.parseInt(new String(chars, i + 2, 4), 16);
252+
sb.append((char) code);
253+
i += 6;
254+
} else if ((second >= 'A' && second <= 'Z') || (second >= 'a' && second <= 'z') || (second >= '0' && second <= '9')) {
255+
// normal '.' or '/'
256+
sb.append('.');
257+
i++;
258+
} else if (second == '_') {
259+
if (i + 2 == chars.length) {
260+
// Ending with "__", which is a possible long name
261+
break;
262+
}
263+
char third = chars[i + 2];
264+
if (third == '0' || third == '1') {
265+
// normal '.' or '/' followed by '_' or unicode character
266+
sb.append('.');
267+
i++;
268+
} else if ((third >= 'A' && third <= 'Z') || (third >= 'a' && third <= 'z') || (third >= '0' && third <= '9') || third == '_') {
269+
// "__" is for long name, the end of class name
270+
break;
271+
}
272+
}
273+
}
274+
}
275+
// find the end of the class name, that is, the last '.'
276+
int lastDot = sb.lastIndexOf(".");
277+
if (lastDot == -1) {
278+
// should not happen
279+
return null;
280+
}
281+
return sb.substring(0, lastDot);
282+
} catch (IndexOutOfBoundsException e) {
283+
// bad jni name?
284+
return null;
285+
}
286+
}
287+
288+
/**
289+
* Enumerates all JNI exported methods in the given library and registers them with the specified class loader.
290+
*
291+
* @param handle the handle of the library, as returned by {@link DlExtLibraryLoader#dlopen}
292+
* @param elfData the file content of the native library, should be the same as the one passed to {@link DlExtLibraryLoader#dlopen}
293+
* @param classLoader the class loader to register the methods with
294+
* @return a summary of the registration process
295+
*/
296+
public static RegistrationSummary registerNativeMethodsForLibrary(
297+
long handle,
298+
@NonNull byte[] elfData,
299+
@NonNull ClassLoader classLoader
300+
) {
301+
Objects.requireNonNull(elfData, "elfData");
302+
Objects.requireNonNull(classLoader, "classLoader");
303+
if (handle == 0) {
304+
throw new IllegalArgumentException("library handle is null");
305+
}
306+
// enumerate all exported symbols in the library starting with "Java_"
307+
ElfView elfView = new ElfView(ByteBuffer.wrap(elfData));
308+
HashSet<String> possibleClassNames = new HashSet<>();
309+
for (Map.Entry<String, Long> symbol : elfView.getDynamicSymbols().entrySet()) {
310+
if (symbol.getKey().startsWith("Java_")) {
311+
String className = getClassNameForJniSymbolName(symbol.getKey());
312+
if (className != null) {
313+
possibleClassNames.add(className);
314+
} else {
315+
// maybe bug in the library?
316+
throw new IllegalStateException("invalid JNI symbol name: " + symbol.getKey());
317+
}
318+
}
319+
}
320+
HashSet<Class<?>> classes = new HashSet<>(possibleClassNames.size());
321+
for (String className : possibleClassNames) {
322+
try {
323+
classes.add(classLoader.loadClass(className));
324+
} catch (ClassNotFoundException e) {
325+
// ignore
326+
}
327+
}
328+
return findAndRegisterNativeMethods(handle, classes.toArray(new Class<?>[0]));
329+
}
330+
189331
}

0 commit comments

Comments
 (0)