33import android .text .TextUtils ;
44
55import androidx .annotation .NonNull ;
6+ import androidx .annotation .Nullable ;
67
78import java .lang .reflect .Method ;
89import java .lang .reflect .Modifier ;
10+ import java .nio .ByteBuffer ;
911import java .util .ArrayList ;
12+ import java .util .HashSet ;
13+ import java .util .Map ;
14+ import java .util .Objects ;
1015
1116import 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