/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package art; import static art.Redefinition.CommonClassDefinition; import java.util.Arrays; import java.util.ArrayList; import java.util.Base64; import java.lang.reflect.*; public class Test944 { static class Transform { public void sayHi() { System.out.println("hello"); } } static class Transform2 { public void sayHi() { System.out.println("hello2"); } } /** * base64 encoded class/dex file for * static class Transform { * public void sayHi() { * System.out.println("Goodbye"); * } * } */ private static CommonClassDefinition TRANSFORM_DEFINITION = new CommonClassDefinition( Transform.class, Base64.getDecoder().decode( "yv66vgAAADQAIAoABgAOCQAPABAIABEKABIAEwcAFQcAGAEABjxpbml0PgEAAygpVgEABENvZGUB" + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAAxUZXN0OTQ0LmphdmEMAAcA" + "CAcAGQwAGgAbAQAHR29vZGJ5ZQcAHAwAHQAeBwAfAQAVYXJ0L1Rlc3Q5NDQkVHJhbnNmb3JtAQAJ" + "VHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9T" + "eXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFt" + "AQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC2FydC9UZXN0OTQ0ACAABQAGAAAA" + "AAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAACgABAAsACAABAAkA" + "AAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAMAAgADQACAAwAAAACAA0AFwAAAAoA" + "AQAFABQAFgAI"), Base64.getDecoder().decode( "ZGV4CjAzNQCFgsuWAAAAAAAAAAAAAAAAAAAAAAAAAAC4AwAAcAAAAHhWNBIAAAAAAAAAAPQCAAAU" + "AAAAcAAAAAkAAADAAAAAAgAAAOQAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAB0AgAARAEAAEQB" + "AABMAQAAVQEAAG4BAAB9AQAAoQEAAMEBAADYAQAA7AEAAAACAAAUAgAAIgIAAC0CAAAwAgAANAIA" + "AEECAABHAgAATAIAAFUCAABcAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" + "DAAAAAgAAAAAAAAADQAAAAgAAABkAgAABwAEABAAAAAAAAAAAAAAAAAAAAASAAAABAABABEAAAAF" + "AAAAAAAAAAAAAAAAAAAABQAAAAAAAAAKAAAA5AIAALgCAAAAAAAABjxpbml0PgAHR29vZGJ5ZQAX" + "TGFydC9UZXN0OTQ0JFRyYW5zZm9ybTsADUxhcnQvVGVzdDk0NDsAIkxkYWx2aWsvYW5ub3RhdGlv" + "bi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEv" + "aW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAS" + "TGphdmEvbGFuZy9TeXN0ZW07AAxUZXN0OTQ0LmphdmEACVRyYW5zZm9ybQABVgACVkwAC2FjY2Vz" + "c0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQAAAQAAAAYAAAAKAAcOAAwA" + "Bw4BCA8AAAAAAQABAAEAAABsAgAABAAAAHAQAwAAAA4AAwABAAIAAABxAgAACQAAAGIAAAAbAQEA" + "AABuIAIAEAAOAAAAAAABAQCAgAT8BAEBlAUAAAICARMYAQIDAg4ECA8XCwACAAAAyAIAAM4CAADY" + "AgAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAJAAAAwAAAAAMA" + "AAACAAAA5AAAAAQAAAABAAAA/AAAAAUAAAAEAAAABAEAAAYAAAABAAAAJAEAAAIgAAAUAAAARAEA" + "AAEQAAABAAAAZAIAAAMgAAACAAAAbAIAAAEgAAACAAAAfAIAAAAgAAABAAAAuAIAAAQgAAACAAAA" + "yAIAAAMQAAABAAAA2AIAAAYgAAABAAAA5AIAAAAQAAABAAAA9AIAAA==")); /** * base64 encoded class/dex file for * static class Transform2 { * public void sayHi() { * System.out.println("Goodbye2"); * } * } */ private static CommonClassDefinition TRANSFORM2_DEFINITION = new CommonClassDefinition( Transform2.class, Base64.getDecoder().decode( "yv66vgAAADQAIAoABgAOCQAPABAIABEKABIAEwcAFQcAGAEABjxpbml0PgEAAygpVgEABENvZGUB" + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAAxUZXN0OTQ0LmphdmEMAAcA" + "CAcAGQwAGgAbAQAIR29vZGJ5ZTIHABwMAB0AHgcAHwEAFmFydC9UZXN0OTQ0JFRyYW5zZm9ybTIB" + "AApUcmFuc2Zvcm0yAQAMSW5uZXJDbGFzc2VzAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFu" + "Zy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3Ry" + "ZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAC2FydC9UZXN0OTQ0ACAABQAG" + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAABQABAAsACAAB" + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAAHAAgACAACAAwAAAACAA0AFwAA" + "AAoAAQAFABQAFgAI"), Base64.getDecoder().decode( "ZGV4CjAzNQAUg8BCAAAAAAAAAAAAAAAAAAAAAAAAAAC8AwAAcAAAAHhWNBIAAAAAAAAAAPgCAAAU" + "AAAAcAAAAAkAAADAAAAAAgAAAOQAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAB4AgAARAEAAEQB" + "AABMAQAAVgEAAHABAAB/AQAAowEAAMMBAADaAQAA7gEAAAICAAAWAgAAJAIAADACAAAzAgAANwIA" + "AEQCAABKAgAATwIAAFgCAABfAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" + "DAAAAAgAAAAAAAAADQAAAAgAAABoAgAABwAEABAAAAAAAAAAAAAAAAAAAAASAAAABAABABEAAAAF" + "AAAAAAAAAAAAAAAAAAAABQAAAAAAAAAKAAAA6AIAALwCAAAAAAAABjxpbml0PgAIR29vZGJ5ZTIA" + "GExhcnQvVGVzdDk0NCRUcmFuc2Zvcm0yOwANTGFydC9UZXN0OTQ0OwAiTGRhbHZpay9hbm5vdGF0" + "aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2" + "YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7" + "ABJMamF2YS9sYW5nL1N5c3RlbTsADFRlc3Q5NDQuamF2YQAKVHJhbnNmb3JtMgABVgACVkwAC2Fj" + "Y2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQAAAAEAAAAGAAAABQAH" + "DgAHAAcOAQgPAAAAAAEAAQABAAAAcAIAAAQAAABwEAMAAAAOAAMAAQACAAAAdQIAAAkAAABiAAAA" + "GwEBAAAAbiACABAADgAAAAAAAQEAgIAEgAUBAZgFAAACAgETGAECAwIOBAgPFwsAAgAAAMwCAADS" + "AgAA3AIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAFAAAAHAAAAACAAAACQAAAMAA" + "AAADAAAAAgAAAOQAAAAEAAAAAQAAAPwAAAAFAAAABAAAAAQBAAAGAAAAAQAAACQBAAACIAAAFAAA" + "AEQBAAABEAAAAQAAAGgCAAADIAAAAgAAAHACAAABIAAAAgAAAIACAAAAIAAAAQAAALwCAAAEIAAA" + "AgAAAMwCAAADEAAAAQAAANwCAAAGIAAAAQAAAOgCAAAAEAAAAQAAAPgCAAA=")); public static void run() throws Exception { Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); doTest(); System.out.println("Passed"); } private static void checkIsInstance(Class klass, Object o) throws Exception { if (!klass.isInstance(o)) { throw new Exception(klass + " is not the class of " + o); } } private static boolean arrayContains(long[] arr, long value) { if (arr == null) { return false; } for (int i = 0; i < arr.length; i++) { if (arr[i] == value) { return true; } } return false; } /** * Checks that we can find the dex-file for the given class in its classloader. * * Throws if it fails. */ private static void checkDexFileInClassLoader(Class klass) throws Exception { // If all the android BCP classes were availible when compiling this test and access checks // weren't a thing this function would be written as follows: // // long dexFilePtr = getDexFilePointer(klass); // dalvik.system.BaseDexClassLoader loader = // (dalvik.system.BaseDexClassLoader)klass.getClassLoader(); // dalvik.system.DexPathList pathListValue = loader.pathList; // dalvik.system.DexPathList.Element[] elementArrayValue = pathListValue.dexElements; // int array_length = elementArrayValue.length; // for (int i = 0; i < array_length; i++) { // dalvik.system.DexPathList.Element curElement = elementArrayValue[i]; // dalvik.system.DexFile curDexFile = curElement.dexFile; // if (curDexFile == null) { // continue; // } // long[] curCookie = (long[])curDexFile.mCookie; // long[] curInternalCookie = (long[])curDexFile.mInternalCookie; // if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) { // return; // } // } // throw new Exception( // "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass); // Get all the fields and classes we need by reflection. Class baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList"); Class dexPathListClass = Class.forName("dalvik.system.DexPathList"); Field elementArrayField = dexPathListClass.getDeclaredField("dexElements"); Class dexPathListElementClass = Class.forName("dalvik.system.DexPathList$Element"); Field dexFileField = dexPathListElementClass.getDeclaredField("dexFile"); Class dexFileClass = Class.forName("dalvik.system.DexFile"); Field dexFileCookieField = dexFileClass.getDeclaredField("mCookie"); Field dexFileInternalCookieField = dexFileClass.getDeclaredField("mInternalCookie"); // Make all the fields accessible AccessibleObject.setAccessible(new AccessibleObject[] { pathListField, elementArrayField, dexFileField, dexFileCookieField, dexFileInternalCookieField }, true); long dexFilePtr = getDexFilePointer(klass); ClassLoader loader = klass.getClassLoader(); checkIsInstance(baseDexClassLoaderClass, loader); // DexPathList pathListValue = ((BaseDexClassLoader) loader).pathList; Object pathListValue = pathListField.get(loader); checkIsInstance(dexPathListClass, pathListValue); // DexPathList.Element[] elementArrayValue = pathListValue.dexElements; Object elementArrayValue = elementArrayField.get(pathListValue); if (!elementArrayValue.getClass().isArray() || elementArrayValue.getClass().getComponentType() != dexPathListElementClass) { throw new Exception("elementArrayValue is not an " + dexPathListElementClass + " array!"); } // int array_length = elementArrayValue.length; int array_length = Array.getLength(elementArrayValue); for (int i = 0; i < array_length; i++) { // DexPathList.Element curElement = elementArrayValue[i]; Object curElement = Array.get(elementArrayValue, i); checkIsInstance(dexPathListElementClass, curElement); // DexFile curDexFile = curElement.dexFile; Object curDexFile = dexFileField.get(curElement); if (curDexFile == null) { continue; } checkIsInstance(dexFileClass, curDexFile); // long[] curCookie = (long[])curDexFile.mCookie; long[] curCookie = (long[])dexFileCookieField.get(curDexFile); // long[] curInternalCookie = (long[])curDexFile.mInternalCookie; long[] curInternalCookie = (long[])dexFileInternalCookieField.get(curDexFile); if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) { return; } } throw new Exception( "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass); } private static void doTest() throws Exception { Transform t = new Transform(); Transform2 t2 = new Transform2(); long initial_t1_dex = getDexFilePointer(Transform.class); long initial_t2_dex = getDexFilePointer(Transform2.class); if (initial_t2_dex != initial_t1_dex) { throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " + "have different initial dex files!"); } checkDexFileInClassLoader(Transform.class); checkDexFileInClassLoader(Transform2.class); // Make sure they are loaded t.sayHi(); t2.sayHi(); // Redefine both of the classes. Redefinition.doMultiClassRedefinition(TRANSFORM_DEFINITION, TRANSFORM2_DEFINITION); // Make sure we actually transformed them! t.sayHi(); t2.sayHi(); long final_t1_dex = getDexFilePointer(Transform.class); long final_t2_dex = getDexFilePointer(Transform2.class); if (final_t2_dex == final_t1_dex) { throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " + "have the same initial dex files!"); } else if (final_t1_dex == initial_t1_dex) { throw new Exception("The class " + Transform.class + " did not get a new dex file!"); } else if (final_t2_dex == initial_t2_dex) { throw new Exception("The class " + Transform2.class + " did not get a new dex file!"); } // Check to make sure the new dex files are in the class loader. checkDexFileInClassLoader(Transform.class); checkDexFileInClassLoader(Transform2.class); } // Gets the 'long' (really a native pointer) that is stored in the ClassLoader representing the // DexFile a class is loaded from. This is plucked out of the internal DexCache object associated // with the class. private static long getDexFilePointer(Class target) throws Exception { // If all the android BCP classes were available when compiling this test and access checks // weren't a thing this function would be written as follows: // // java.lang.DexCache dexCacheObject = target.dexCache; // if (dexCacheObject == null) { // return 0; // } // return dexCacheObject.dexFile; Field dexCacheField = Class.class.getDeclaredField("dexCache"); Class dexCacheClass = Class.forName("java.lang.DexCache"); Field dexFileField = dexCacheClass.getDeclaredField("dexFile"); AccessibleObject.setAccessible(new AccessibleObject[] { dexCacheField, dexFileField }, true); Object dexCacheObject = dexCacheField.get(target); if (dexCacheObject == null) { return 0; } checkIsInstance(dexCacheClass, dexCacheObject); return dexFileField.getLong(dexCacheObject); } }