1 /* 2 * Copyright (C) 2007 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 package dalvik.system; 18 19 import android.system.ErrnoException; 20 import android.system.StructStat; 21 import java.io.File; 22 import java.io.FileNotFoundException; 23 import java.io.IOException; 24 import java.util.ArrayList; 25 import java.util.Enumeration; 26 import java.util.List; 27 import libcore.io.Libcore; 28 29 /** 30 * Manipulates DEX files. The class is similar in principle to 31 * {@link java.util.zip.ZipFile}. It is used primarily by class loaders. 32 * <p> 33 * Note we don't directly open and read the DEX file here. They're memory-mapped 34 * read-only by the VM. 35 */ 36 public final class DexFile { 37 private long mCookie; 38 private final String mFileName; 39 private final CloseGuard guard = CloseGuard.get(); 40 41 /** 42 * Opens a DEX file from a given File object. This will usually be a ZIP/JAR 43 * file with a "classes.dex" inside. 44 * 45 * The VM will generate the name of the corresponding file in 46 * /data/dalvik-cache and open it, possibly creating or updating 47 * it first if system permissions allow. Don't pass in the name of 48 * a file in /data/dalvik-cache, as the named file is expected to be 49 * in its original (pre-dexopt) state. 50 * 51 * @param file 52 * the File object referencing the actual DEX file 53 * 54 * @throws IOException 55 * if an I/O error occurs, such as the file not being found or 56 * access rights missing for opening it 57 */ DexFile(File file)58 public DexFile(File file) throws IOException { 59 this(file.getPath()); 60 } 61 62 /** 63 * Opens a DEX file from a given filename. This will usually be a ZIP/JAR 64 * file with a "classes.dex" inside. 65 * 66 * The VM will generate the name of the corresponding file in 67 * /data/dalvik-cache and open it, possibly creating or updating 68 * it first if system permissions allow. Don't pass in the name of 69 * a file in /data/dalvik-cache, as the named file is expected to be 70 * in its original (pre-dexopt) state. 71 * 72 * @param fileName 73 * the filename of the DEX file 74 * 75 * @throws IOException 76 * if an I/O error occurs, such as the file not being found or 77 * access rights missing for opening it 78 */ DexFile(String fileName)79 public DexFile(String fileName) throws IOException { 80 mCookie = openDexFile(fileName, null, 0); 81 mFileName = fileName; 82 guard.open("close"); 83 //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName); 84 } 85 86 /** 87 * Opens a DEX file from a given filename, using a specified file 88 * to hold the optimized data. 89 * 90 * @param sourceName 91 * Jar or APK file with "classes.dex". 92 * @param outputName 93 * File that will hold the optimized form of the DEX data. 94 * @param flags 95 * Enable optional features. 96 */ DexFile(String sourceName, String outputName, int flags)97 private DexFile(String sourceName, String outputName, int flags) throws IOException { 98 if (outputName != null) { 99 try { 100 String parent = new File(outputName).getParent(); 101 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { 102 throw new IllegalArgumentException("Optimized data directory " + parent 103 + " is not owned by the current user. Shared storage cannot protect" 104 + " your application from code injection attacks."); 105 } 106 } catch (ErrnoException ignored) { 107 // assume we'll fail with a more contextual error later 108 } 109 } 110 111 mCookie = openDexFile(sourceName, outputName, flags); 112 mFileName = sourceName; 113 guard.open("close"); 114 //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); 115 } 116 117 /** 118 * Open a DEX file, specifying the file in which the optimized DEX 119 * data should be written. If the optimized form exists and appears 120 * to be current, it will be used; if not, the VM will attempt to 121 * regenerate it. 122 * 123 * This is intended for use by applications that wish to download 124 * and execute DEX files outside the usual application installation 125 * mechanism. This function should not be called directly by an 126 * application; instead, use a class loader such as 127 * dalvik.system.DexClassLoader. 128 * 129 * @param sourcePathName 130 * Jar or APK file with "classes.dex". (May expand this to include 131 * "raw DEX" in the future.) 132 * @param outputPathName 133 * File that will hold the optimized form of the DEX data. 134 * @param flags 135 * Enable optional features. (Currently none defined.) 136 * @return 137 * A new or previously-opened DexFile. 138 * @throws IOException 139 * If unable to open the source or output file. 140 */ loadDex(String sourcePathName, String outputPathName, int flags)141 static public DexFile loadDex(String sourcePathName, String outputPathName, 142 int flags) throws IOException { 143 144 /* 145 * TODO: we may want to cache previously-opened DexFile objects. 146 * The cache would be synchronized with close(). This would help 147 * us avoid mapping the same DEX more than once when an app 148 * decided to open it multiple times. In practice this may not 149 * be a real issue. 150 */ 151 return new DexFile(sourcePathName, outputPathName, flags); 152 } 153 154 /** 155 * Gets the name of the (already opened) DEX file. 156 * 157 * @return the file name 158 */ getName()159 public String getName() { 160 return mFileName; 161 } 162 toString()163 @Override public String toString() { 164 return getName(); 165 } 166 167 /** 168 * Closes the DEX file. 169 * <p> 170 * This may not be able to release any resources. If classes from this 171 * DEX file are still resident, the DEX file can't be unmapped. 172 * 173 * @throws IOException 174 * if an I/O error occurs during closing the file, which 175 * normally should not happen 176 */ close()177 public void close() throws IOException { 178 if (mCookie != 0) { 179 guard.close(); 180 closeDexFile(mCookie); 181 mCookie = 0; 182 } 183 } 184 185 /** 186 * Loads a class. Returns the class on success, or a {@code null} reference 187 * on failure. 188 * <p> 189 * If you are not calling this from a class loader, this is most likely not 190 * going to do what you want. Use {@link Class#forName(String)} instead. 191 * <p> 192 * The method does not throw {@link ClassNotFoundException} if the class 193 * isn't found because it isn't reasonable to throw exceptions wildly every 194 * time a class is not found in the first DEX file we look at. 195 * 196 * @param name 197 * the class name, which should look like "java/lang/String" 198 * 199 * @param loader 200 * the class loader that tries to load the class (in most cases 201 * the caller of the method 202 * 203 * @return the {@link Class} object representing the class, or {@code null} 204 * if the class cannot be loaded 205 */ loadClass(String name, ClassLoader loader)206 public Class loadClass(String name, ClassLoader loader) { 207 String slashName = name.replace('.', '/'); 208 return loadClassBinaryName(slashName, loader, null); 209 } 210 211 /** 212 * See {@link #loadClass(String, ClassLoader)}. 213 * 214 * This takes a "binary" class name to better match ClassLoader semantics. 215 * 216 * @hide 217 */ loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed)218 public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { 219 return defineClass(name, loader, mCookie, suppressed); 220 } 221 defineClass(String name, ClassLoader loader, long cookie, List<Throwable> suppressed)222 private static Class defineClass(String name, ClassLoader loader, long cookie, 223 List<Throwable> suppressed) { 224 Class result = null; 225 try { 226 result = defineClassNative(name, loader, cookie); 227 } catch (NoClassDefFoundError e) { 228 if (suppressed != null) { 229 suppressed.add(e); 230 } 231 } catch (ClassNotFoundException e) { 232 if (suppressed != null) { 233 suppressed.add(e); 234 } 235 } 236 return result; 237 } 238 239 /** 240 * Enumerate the names of the classes in this DEX file. 241 * 242 * @return an enumeration of names of classes contained in the DEX file, in 243 * the usual internal form (like "java/lang/String"). 244 */ entries()245 public Enumeration<String> entries() { 246 return new DFEnum(this); 247 } 248 249 /* 250 * Helper class. 251 */ 252 private class DFEnum implements Enumeration<String> { 253 private int mIndex; 254 private String[] mNameList; 255 DFEnum(DexFile df)256 DFEnum(DexFile df) { 257 mIndex = 0; 258 mNameList = getClassNameList(mCookie); 259 } 260 hasMoreElements()261 public boolean hasMoreElements() { 262 return (mIndex < mNameList.length); 263 } 264 nextElement()265 public String nextElement() { 266 return mNameList[mIndex++]; 267 } 268 } 269 270 /** 271 * Called when the class is finalized. Makes sure the DEX file is closed. 272 * 273 * @throws IOException 274 * if an I/O error occurs during closing the file, which 275 * normally should not happen 276 */ finalize()277 @Override protected void finalize() throws Throwable { 278 try { 279 if (guard != null) { 280 guard.warnIfOpen(); 281 } 282 close(); 283 } finally { 284 super.finalize(); 285 } 286 } 287 288 289 /* 290 * Open a DEX file. The value returned is a magic VM cookie. On 291 * failure, an IOException is thrown. 292 */ openDexFile(String sourceName, String outputName, int flags)293 private static long openDexFile(String sourceName, String outputName, int flags) throws IOException { 294 // Use absolute paths to enable the use of relative paths when testing on host. 295 return openDexFileNative(new File(sourceName).getAbsolutePath(), 296 (outputName == null) ? null : new File(outputName).getAbsolutePath(), 297 flags); 298 } 299 closeDexFile(long cookie)300 private static native void closeDexFile(long cookie); defineClassNative(String name, ClassLoader loader, long cookie)301 private static native Class defineClassNative(String name, ClassLoader loader, long cookie) 302 throws ClassNotFoundException, NoClassDefFoundError; getClassNameList(long cookie)303 private static native String[] getClassNameList(long cookie); 304 /* 305 * Open a DEX file. The value returned is a magic VM cookie. On 306 * failure, an IOException is thrown. 307 */ openDexFileNative(String sourceName, String outputName, int flags)308 private static native long openDexFileNative(String sourceName, String outputName, int flags); 309 310 /** 311 * Returns true if the VM believes that the apk/jar file is out of date 312 * and should be passed through "dexopt" again. 313 * 314 * @param fileName the absolute path to the apk/jar file to examine. 315 * @return true if dexopt should be called on the file, false otherwise. 316 * @throws java.io.FileNotFoundException if fileName is not readable, 317 * not a file, or not present. 318 * @throws java.io.IOException if fileName is not a valid apk/jar file or 319 * if problems occur while parsing it. 320 * @throws java.lang.NullPointerException if fileName is null. 321 * @throws dalvik.system.StaleDexCacheError if the optimized dex file 322 * is stale but exists on a read-only partition. 323 */ isDexOptNeeded(String fileName)324 public static native boolean isDexOptNeeded(String fileName) 325 throws FileNotFoundException, IOException; 326 327 /** 328 * See {@link #isDexOptNeededInternal(String, String, String, boolean)}. 329 * 330 * @hide 331 */ 332 public static final byte UP_TO_DATE = 0; 333 334 /** 335 * See {@link #isDexOptNeededInternal(String, String, String, boolean)}. 336 * 337 * @hide 338 */ 339 public static final byte PATCHOAT_NEEDED = 1; 340 341 /** 342 * See {@link #isDexOptNeededInternal(String, String, String, boolean)}. 343 * 344 * @hide 345 */ 346 public static final byte DEXOPT_NEEDED = 2; 347 348 /** 349 * Returns UP_TO_DATE if the VM believes that the apk/jar file 350 * is up to date, PATCHOAT_NEEDED if it believes that the file is up 351 * to date but it must be relocated to match the base address offset, 352 * and DEXOPT_NEEDED if it believes that it is out of date and should 353 * be passed through "dexopt" again. 354 * 355 * @param fileName the absolute path to the apk/jar file to examine. 356 * @return DEXOPT_NEEDED if dexopt should be called on the file, 357 * PATCHOAT_NEEDED if we need to run "patchoat" on it and 358 * UP_TO_DATE otherwise. 359 * @throws java.io.FileNotFoundException if fileName is not readable, 360 * not a file, or not present. 361 * @throws java.io.IOException if fileName is not a valid apk/jar file or 362 * if problems occur while parsing it. 363 * @throws java.lang.NullPointerException if fileName is null. 364 * @throws dalvik.system.StaleDexCacheError if the optimized dex file 365 * is stale but exists on a read-only partition. 366 * 367 * @hide 368 */ isDexOptNeededInternal(String fileName, String pkgname, String instructionSet, boolean defer)369 public static native byte isDexOptNeededInternal(String fileName, String pkgname, 370 String instructionSet, boolean defer) 371 throws FileNotFoundException, IOException; 372 } 373