/* * 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. */ import java.lang.reflect.Method; import java.util.Enumeration; import java.nio.file.Files; import java.nio.file.Paths; /** * DexFile tests (Dalvik-specific). */ public class Main { private static final String CLASS_PATH = System.getenv("DEX_LOCATION") + "/071-dexfile-map-clean-ex.jar"; /** * Prep the environment then run the test. */ public static void main(String[] args) throws Exception { // Load the dex file, this is a pre-requisite to mmap-ing it in. Class<?> AnotherClass = testDexFile(); // Check that the memory maps are clean. testDexMemoryMaps(); // Prevent garbage collector from collecting our DexFile // (and unmapping too early) by using it after we finish // our verification. AnotherClass.newInstance(); } private static boolean checkSmapsEntry(String[] smapsLines, int offset) { String nameDescription = smapsLines[offset]; String[] split = nameDescription.split(" "); String permissions = split[1]; // Mapped as read-only + anonymous. if (!permissions.startsWith("r--p")) { return false; } boolean validated = false; // We have the right entry, now make sure it's valid. for (int i = offset; i < smapsLines.length; ++i) { String line = smapsLines[i]; if (line.startsWith("Shared_Dirty") || line.startsWith("Private_Dirty")) { String lineTrimmed = line.trim(); String[] lineSplit = lineTrimmed.split(" +"); String sizeUsuallyInKb = lineSplit[lineSplit.length - 2]; sizeUsuallyInKb = sizeUsuallyInKb.trim(); if (!sizeUsuallyInKb.equals("0")) { System.out.println( "ERROR: Memory mapping for " + CLASS_PATH + " is unexpectedly dirty"); System.out.println(line); } else { validated = true; } } // VmFlags marks the "end" of an smaps entry. if (line.startsWith("VmFlags")) { break; } } if (validated) { System.out.println("Secondary dexfile mmap is clean"); } else { System.out.println("ERROR: Memory mapping is missing Shared_Dirty/Private_Dirty entries"); } return true; } // This test takes relies on dex2oat being skipped. // (enforced in 'run' file by using '--no-dex2oat' // // This could happen in a non-test situation // if a secondary dex file is loaded (but not yet maintenance-mode compiled) // with JIT. // // Or it could also happen if a secondary dex file is loaded and forced // into running into the interpreter (e.g. duplicate classes). // // Rather than relying on those weird fallbacks, // we force the runtime not to dex2oat the dex file to ensure // this test is repeatable and less brittle. private static void testDexMemoryMaps() throws Exception { // Ensure that the secondary dex file is mapped clean (directly from JAR file). String smaps = new String(Files.readAllBytes(Paths.get("/proc/self/smaps"))); String[] smapsLines = smaps.split("\n"); boolean found = true; for (int i = 0; i < smapsLines.length; ++i) { if (smapsLines[i].contains(CLASS_PATH)) { if (checkSmapsEntry(smapsLines, i)) { return; } // else we found the wrong one, keep going. } } // Error case: System.out.println("Could not find " + CLASS_PATH + " RO-anonymous smaps entry"); System.out.println(smaps); } private static Class<?> testDexFile() throws Exception { ClassLoader classLoader = Main.class.getClassLoader(); Class<?> DexFile = classLoader.loadClass("dalvik.system.DexFile"); Method DexFile_loadDex = DexFile.getMethod("loadDex", String.class, String.class, Integer.TYPE); Method DexFile_entries = DexFile.getMethod("entries"); Object dexFile = DexFile_loadDex.invoke(null, CLASS_PATH, null, 0); Enumeration<String> e = (Enumeration<String>) DexFile_entries.invoke(dexFile); while (e.hasMoreElements()) { String className = e.nextElement(); System.out.println(className); } Method DexFile_loadClass = DexFile.getMethod("loadClass", String.class, ClassLoader.class); Class<?> AnotherClass = (Class<?>)DexFile_loadClass.invoke(dexFile, "Another", Main.class.getClassLoader()); return AnotherClass; } }