1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import java.lang.reflect.Field; 18 import java.lang.reflect.Method; 19 import java.util.List; 20 21 class MyClassLoader extends ClassLoader { MyClassLoader()22 MyClassLoader() throws Exception { 23 super(MyClassLoader.class.getClassLoader()); 24 25 // Some magic to get access to the pathList field of BaseDexClassLoader. 26 ClassLoader loader = getClass().getClassLoader(); 27 Class<?> baseDexClassLoader = loader.getClass().getSuperclass(); 28 Field f = baseDexClassLoader.getDeclaredField("pathList"); 29 f.setAccessible(true); 30 Object pathList = f.get(loader); 31 32 // Some magic to get access to the dexField field of pathList. 33 f = pathList.getClass().getDeclaredField("dexElements"); 34 f.setAccessible(true); 35 dexElements = (Object[]) f.get(pathList); 36 dexFileField = dexElements[0].getClass().getDeclaredField("dexFile"); 37 dexFileField.setAccessible(true); 38 } 39 40 Object[] dexElements; 41 Field dexFileField; 42 43 static ClassLoader level1ClassLoader; 44 loadClass(String className, boolean resolve)45 protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { 46 if (this != level1ClassLoader) { 47 if (className.equals("Level1")) { 48 return level1ClassLoader.loadClass(className); 49 } else if (className.equals("Level2")) { 50 throw new ClassNotFoundException("None of my methods require Level2!"); 51 } else if (!className.equals("LoadedByMyClassLoader")) { 52 // We're only going to handle LoadedByMyClassLoader. 53 return getParent().loadClass(className); 54 } 55 } else { 56 if (className != "Level1" && className != "Level2") { 57 return getParent().loadClass(className); 58 } 59 } 60 61 // Mimic what DexPathList.findClass is doing. 62 try { 63 for (Object element : dexElements) { 64 Object dex = dexFileField.get(element); 65 Method method = dex.getClass().getDeclaredMethod( 66 "loadClassBinaryName", String.class, ClassLoader.class, List.class); 67 68 if (dex != null) { 69 Class clazz = (Class)method.invoke(dex, className, this, null); 70 if (clazz != null) { 71 return clazz; 72 } 73 } 74 } 75 } catch (Exception e) { /* Ignore */ } 76 return null; 77 } 78 } 79 80 class LoadedByMyClassLoader { bar()81 public static void bar() { 82 Level1.$inline$bar(); 83 } 84 } 85 86 class Main { main(String[] args)87 public static void main(String[] args) throws Exception { 88 System.loadLibrary(args[0]); 89 // Clone resolved methods, to restore the original version just 90 // before we walk the stack in $noinline$bar. 91 savedResolvedMethods = cloneResolvedMethods(Main.class); 92 93 MyClassLoader o = new MyClassLoader(); 94 MyClassLoader.level1ClassLoader = new MyClassLoader(); 95 Class foo = o.loadClass("LoadedByMyClassLoader"); 96 Method m = foo.getDeclaredMethod("bar"); 97 try { 98 m.invoke(null); 99 } catch (Error e) { /* Ignore */ } 100 } 101 $inline$bar()102 public static void $inline$bar() { 103 } 104 $noinline$bar()105 public static void $noinline$bar() { 106 try { 107 // Be evil and clear all dex cache entries. 108 Field f = Class.class.getDeclaredField("dexCache"); 109 f.setAccessible(true); 110 Object dexCache = f.get(Main.class); 111 f = dexCache.getClass().getDeclaredField("resolvedTypes"); 112 f.setAccessible(true); 113 Object[] array = (Object[]) f.get(dexCache); 114 for (int i = 0; i < array.length; i++) { 115 array[i] = null; 116 } 117 restoreResolvedMethods(Main.class, savedResolvedMethods); 118 } catch (Throwable t) { /* Ignore */ } 119 120 // This will walk the stack, trying to resolve methods in it. 121 // Because we cleared dex cache entries, we will have to find 122 // classes again, which require to use the correct class loader 123 // in the presence of inlining. 124 new Exception().printStackTrace(); 125 } 126 static Object savedResolvedMethods; 127 cloneResolvedMethods(Class<?> cls)128 static native Object cloneResolvedMethods(Class<?> cls); restoreResolvedMethods(Class<?> cls, Object saved)129 static native void restoreResolvedMethods(Class<?> cls, Object saved); 130 } 131