1 /*
2  * Copyright (C) 2017 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.Method;
18 import java.util.Enumeration;
19 
20 import java.nio.file.Files;
21 import java.nio.file.Paths;
22 
23 /**
24  * DexFile tests (Dalvik-specific).
25  */
26 public class Main {
27     private static final String CLASS_PATH =
28         System.getenv("DEX_LOCATION") + "/071-dexfile-map-clean-ex.jar";
29 
30     /**
31      * Prep the environment then run the test.
32      */
main(String[] args)33     public static void main(String[] args) throws Exception {
34         // Load the dex file, this is a pre-requisite to mmap-ing it in.
35         Class<?> AnotherClass = testDexFile();
36         // Check that the memory maps are clean.
37         testDexMemoryMaps();
38 
39         // Prevent garbage collector from collecting our DexFile
40         // (and unmapping too early) by using it after we finish
41         // our verification.
42         AnotherClass.newInstance();
43     }
44 
checkSmapsEntry(String[] smapsLines, int offset)45     private static boolean checkSmapsEntry(String[] smapsLines, int offset) {
46       String nameDescription = smapsLines[offset];
47       String[] split = nameDescription.split(" ");
48 
49       String permissions = split[1];
50       // Mapped as read-only + anonymous.
51       if (!permissions.startsWith("r--p")) {
52         return false;
53       }
54 
55       boolean validated = false;
56 
57       // We have the right entry, now make sure it's valid.
58       for (int i = offset; i < smapsLines.length; ++i) {
59         String line = smapsLines[i];
60 
61         if (line.startsWith("Shared_Dirty") || line.startsWith("Private_Dirty")) {
62           String lineTrimmed = line.trim();
63           String[] lineSplit = lineTrimmed.split(" +");
64 
65           String sizeUsuallyInKb = lineSplit[lineSplit.length - 2];
66 
67           sizeUsuallyInKb = sizeUsuallyInKb.trim();
68 
69           if (!sizeUsuallyInKb.equals("0")) {
70             System.out.println(
71                 "ERROR: Memory mapping for " + CLASS_PATH + " is unexpectedly dirty");
72             System.out.println(line);
73           } else {
74             validated = true;
75           }
76         }
77 
78         // VmFlags marks the "end" of an smaps entry.
79         if (line.startsWith("VmFlags")) {
80           break;
81         }
82       }
83 
84       if (validated) {
85         System.out.println("Secondary dexfile mmap is clean");
86       } else {
87         System.out.println("ERROR: Memory mapping is missing Shared_Dirty/Private_Dirty entries");
88       }
89 
90       return true;
91     }
92 
93     // This test takes relies on dex2oat being skipped.
94     // (enforced in 'run' file by using '--no-dex2oat'
95     //
96     // This could happen in a non-test situation
97     // if a secondary dex file is loaded (but not yet maintenance-mode compiled)
98     // with JIT.
99     //
100     // Or it could also happen if a secondary dex file is loaded and forced
101     // into running into the interpreter (e.g. duplicate classes).
102     //
103     // Rather than relying on those weird fallbacks,
104     // we force the runtime not to dex2oat the dex file to ensure
105     // this test is repeatable and less brittle.
testDexMemoryMaps()106     private static void testDexMemoryMaps() throws Exception {
107         // Ensure that the secondary dex file is mapped clean (directly from JAR file).
108         String smaps = new String(Files.readAllBytes(Paths.get("/proc/self/smaps")));
109 
110         String[] smapsLines = smaps.split("\n");
111         boolean found = true;
112         for (int i = 0; i < smapsLines.length; ++i) {
113           if (smapsLines[i].contains(CLASS_PATH)) {
114             if (checkSmapsEntry(smapsLines, i)) {
115               return;
116             } // else we found the wrong one, keep going.
117           }
118         }
119 
120         // Error case:
121         System.out.println("Could not find " + CLASS_PATH + " RO-anonymous smaps entry");
122         System.out.println(smaps);
123     }
124 
testDexFile()125     private static Class<?> testDexFile() throws Exception {
126         ClassLoader classLoader = Main.class.getClassLoader();
127         Class<?> DexFile = classLoader.loadClass("dalvik.system.DexFile");
128         Method DexFile_loadDex = DexFile.getMethod("loadDex",
129                                                    String.class,
130                                                    String.class,
131                                                    Integer.TYPE);
132         Method DexFile_entries = DexFile.getMethod("entries");
133         Object dexFile = DexFile_loadDex.invoke(null, CLASS_PATH, null, 0);
134         Enumeration<String> e = (Enumeration<String>) DexFile_entries.invoke(dexFile);
135         while (e.hasMoreElements()) {
136             String className = e.nextElement();
137             System.out.println(className);
138         }
139 
140         Method DexFile_loadClass = DexFile.getMethod("loadClass",
141                                                      String.class,
142                                                      ClassLoader.class);
143         Class<?> AnotherClass = (Class<?>)DexFile_loadClass.invoke(dexFile,
144             "Another", Main.class.getClassLoader());
145         return AnotherClass;
146     }
147 }
148