1 /* 2 * Copyright (C) 2008 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 /* 18 * dalvik.system.DexFile 19 */ 20 #include "Dalvik.h" 21 #include "native/InternalNativePriv.h" 22 23 24 /* 25 * Internal struct for managing DexFile. 26 */ 27 typedef struct DexOrJar { 28 char* fileName; 29 bool isDex; 30 bool okayToFree; 31 RawDexFile* pRawDexFile; 32 JarFile* pJarFile; 33 } DexOrJar; 34 35 /* 36 * (This is a dvmHashTableFree callback.) 37 */ 38 void dvmFreeDexOrJar(void* vptr) 39 { 40 DexOrJar* pDexOrJar = (DexOrJar*) vptr; 41 42 LOGV("Freeing DexOrJar '%s'\n", pDexOrJar->fileName); 43 44 if (pDexOrJar->isDex) 45 dvmRawDexFileFree(pDexOrJar->pRawDexFile); 46 else 47 dvmJarFileFree(pDexOrJar->pJarFile); 48 free(pDexOrJar->fileName); 49 free(pDexOrJar); 50 } 51 52 /* 53 * (This is a dvmHashTableLookup compare func.) 54 * 55 * Args are DexOrJar*. 56 */ 57 static int hashcmpDexOrJar(const void* tableVal, const void* newVal) 58 { 59 return (int) newVal - (int) tableVal; 60 } 61 62 /* 63 * Verify that the "cookie" is a DEX file we opened. 64 * 65 * Expects that the hash table will be *unlocked* here. 66 * 67 * If the cookie is invalid, we throw an exception and return "false". 68 */ 69 static bool validateCookie(int cookie) 70 { 71 DexOrJar* pDexOrJar = (DexOrJar*) cookie; 72 73 LOGVV("+++ dex verifying cookie %p\n", pDexOrJar); 74 75 if (pDexOrJar == NULL) 76 return false; 77 78 u4 hash = dvmComputeUtf8Hash(pDexOrJar->fileName); 79 dvmHashTableLock(gDvm.userDexFiles); 80 void* result = dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar, 81 hashcmpDexOrJar, false); 82 dvmHashTableUnlock(gDvm.userDexFiles); 83 if (result == NULL) { 84 dvmThrowException("Ljava/lang/RuntimeException;", 85 "invalid DexFile cookie"); 86 return false; 87 } 88 89 return true; 90 } 91 92 /* 93 * private static int openDexFile(String sourceName, String outputName, 94 * int flags) throws IOException 95 * 96 * Open a DEX file, returning a pointer to our internal data structure. 97 * 98 * "sourceName" should point to the "source" jar or DEX file. 99 * 100 * If "outputName" is NULL, the DEX code will automatically find the 101 * "optimized" version in the cache directory, creating it if necessary. 102 * If it's non-NULL, the specified file will be used instead. 103 * 104 * TODO: at present we will happily open the same file more than once. 105 * To optimize this away we could search for existing entries in the hash 106 * table and refCount them. Requires atomic ops or adding "synchronized" 107 * to the non-native code that calls here. 108 */ 109 static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args, 110 JValue* pResult) 111 { 112 StringObject* sourceNameObj = (StringObject*) args[0]; 113 StringObject* outputNameObj = (StringObject*) args[1]; 114 DexOrJar* pDexOrJar = NULL; 115 JarFile* pJarFile; 116 RawDexFile* pRawDexFile; 117 char* sourceName; 118 char* outputName; 119 120 if (sourceNameObj == NULL) { 121 dvmThrowException("Ljava/lang/NullPointerException;", NULL); 122 RETURN_VOID(); 123 } 124 125 sourceName = dvmCreateCstrFromString(sourceNameObj); 126 if (outputNameObj != NULL) 127 outputName = dvmCreateCstrFromString(outputNameObj); 128 else 129 outputName = NULL; 130 131 /* 132 * We have to deal with the possibility that somebody might try to 133 * open one of our bootstrap class DEX files. The set of dependencies 134 * will be different, and hence the results of optimization might be 135 * different, which means we'd actually need to have two versions of 136 * the optimized DEX: one that only knows about part of the boot class 137 * path, and one that knows about everything in it. The latter might 138 * optimize field/method accesses based on a class that appeared later 139 * in the class path. 140 * 141 * We can't let the user-defined class loader open it and start using 142 * the classes, since the optimized form of the code skips some of 143 * the method and field resolution that we would ordinarily do, and 144 * we'd have the wrong semantics. 145 * 146 * We have to reject attempts to manually open a DEX file from the boot 147 * class path. The easiest way to do this is by filename, which works 148 * out because variations in name (e.g. "/system/framework/./ext.jar") 149 * result in us hitting a different dalvik-cache entry. It's also fine 150 * if the caller specifies their own output file. 151 */ 152 if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) { 153 LOGW("Refusing to reopen boot DEX '%s'\n", sourceName); 154 dvmThrowException("Ljava/io/IOException;", 155 "Re-opening BOOTCLASSPATH DEX files is not allowed"); 156 free(sourceName); 157 RETURN_VOID(); 158 } 159 160 /* 161 * Try to open it directly as a DEX. If that fails, try it as a Zip 162 * with a "classes.dex" inside. 163 */ 164 if (dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) { 165 LOGV("Opening DEX file '%s' (DEX)\n", sourceName); 166 167 pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); 168 pDexOrJar->isDex = true; 169 pDexOrJar->pRawDexFile = pRawDexFile; 170 } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) { 171 LOGV("Opening DEX file '%s' (Jar)\n", sourceName); 172 173 pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar)); 174 pDexOrJar->isDex = false; 175 pDexOrJar->pJarFile = pJarFile; 176 } else { 177 LOGV("Unable to open DEX file '%s'\n", sourceName); 178 dvmThrowException("Ljava/io/IOException;", "unable to open DEX file"); 179 } 180 181 if (pDexOrJar != NULL) { 182 pDexOrJar->fileName = sourceName; 183 184 /* add to hash table */ 185 u4 hash = dvmComputeUtf8Hash(sourceName); 186 void* result; 187 dvmHashTableLock(gDvm.userDexFiles); 188 result = dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar, 189 hashcmpDexOrJar, true); 190 dvmHashTableUnlock(gDvm.userDexFiles); 191 if (result != pDexOrJar) { 192 LOGE("Pointer has already been added?\n"); 193 dvmAbort(); 194 } 195 196 pDexOrJar->okayToFree = true; 197 } else 198 free(sourceName); 199 200 RETURN_PTR(pDexOrJar); 201 } 202 203 /* 204 * private static void closeDexFile(int cookie) 205 * 206 * Release resources associated with a user-loaded DEX file. 207 */ 208 static void Dalvik_dalvik_system_DexFile_closeDexFile(const u4* args, 209 JValue* pResult) 210 { 211 int cookie = args[0]; 212 DexOrJar* pDexOrJar = (DexOrJar*) cookie; 213 214 if (pDexOrJar == NULL) 215 RETURN_VOID(); 216 217 LOGV("Closing DEX file %p (%s)\n", pDexOrJar, pDexOrJar->fileName); 218 219 if (!validateCookie(cookie)) 220 RETURN_VOID(); 221 222 /* 223 * We can't just free arbitrary DEX files because they have bits and 224 * pieces of loaded classes. The only exception to this rule is if 225 * they were never used to load classes. 226 * 227 * If we can't free them here, dvmInternalNativeShutdown() will free 228 * them when the VM shuts down. 229 */ 230 if (pDexOrJar->okayToFree) { 231 u4 hash = dvmComputeUtf8Hash(pDexOrJar->fileName); 232 dvmHashTableLock(gDvm.userDexFiles); 233 if (!dvmHashTableRemove(gDvm.userDexFiles, hash, pDexOrJar)) { 234 LOGW("WARNING: could not remove '%s' from DEX hash table\n", 235 pDexOrJar->fileName); 236 } 237 dvmHashTableUnlock(gDvm.userDexFiles); 238 LOGV("+++ freeing DexFile '%s' resources\n", pDexOrJar->fileName); 239 dvmFreeDexOrJar(pDexOrJar); 240 } else { 241 LOGV("+++ NOT freeing DexFile '%s' resources\n", pDexOrJar->fileName); 242 } 243 244 RETURN_VOID(); 245 } 246 247 /* 248 * private static Class defineClass(String name, ClassLoader loader, 249 * int cookie, ProtectionDomain pd) 250 * 251 * Load a class from a DEX file. This is roughly equivalent to defineClass() 252 * in a regular VM -- it's invoked by the class loader to cause the 253 * creation of a specific class. The difference is that the search for and 254 * reading of the bytes is done within the VM. 255 * 256 * The class name is a "binary name", e.g. "java.lang.String". 257 * 258 * Returns a null pointer with no exception if the class was not found. 259 * Throws an exception on other failures. 260 */ 261 static void Dalvik_dalvik_system_DexFile_defineClass(const u4* args, 262 JValue* pResult) 263 { 264 StringObject* nameObj = (StringObject*) args[0]; 265 Object* loader = (Object*) args[1]; 266 int cookie = args[2]; 267 Object* pd = (Object*) args[3]; 268 ClassObject* clazz = NULL; 269 DexOrJar* pDexOrJar = (DexOrJar*) cookie; 270 DvmDex* pDvmDex; 271 char* name; 272 char* descriptor; 273 274 name = dvmCreateCstrFromString(nameObj); 275 descriptor = dvmDotToDescriptor(name); 276 LOGV("--- Explicit class load '%s' 0x%08x\n", descriptor, cookie); 277 free(name); 278 279 if (!validateCookie(cookie)) 280 RETURN_VOID(); 281 282 if (pDexOrJar->isDex) 283 pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile); 284 else 285 pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile); 286 287 /* once we load something, we can't unmap the storage */ 288 pDexOrJar->okayToFree = false; 289 290 clazz = dvmDefineClass(pDvmDex, descriptor, loader); 291 Thread* self = dvmThreadSelf(); 292 if (dvmCheckException(self)) { 293 /* 294 * If we threw a "class not found" exception, stifle it, since the 295 * contract in the higher method says we simply return null if 296 * the class is not found. 297 */ 298 Object* excep = dvmGetException(self); 299 if (strcmp(excep->clazz->descriptor, 300 "Ljava/lang/ClassNotFoundException;") == 0 || 301 strcmp(excep->clazz->descriptor, 302 "Ljava/lang/NoClassDefFoundError;") == 0) 303 { 304 dvmClearException(self); 305 } 306 clazz = NULL; 307 } 308 309 /* 310 * Set the ProtectionDomain -- do we need this to happen before we 311 * link the class and make it available? If so, we need to pass it 312 * through dvmDefineClass (and figure out some other 313 * stuff, like where it comes from for bootstrap classes). 314 */ 315 if (clazz != NULL) { 316 //LOGI("SETTING pd '%s' to %p\n", clazz->descriptor, pd); 317 dvmSetFieldObject((Object*) clazz, gDvm.offJavaLangClass_pd, pd); 318 } 319 320 free(descriptor); 321 RETURN_PTR(clazz); 322 } 323 324 /* 325 * private static String[] getClassNameList(int cookie) 326 * 327 * Returns a String array that holds the names of all classes in the 328 * specified DEX file. 329 */ 330 static void Dalvik_dalvik_system_DexFile_getClassNameList(const u4* args, 331 JValue* pResult) 332 { 333 int cookie = args[0]; 334 DexOrJar* pDexOrJar = (DexOrJar*) cookie; 335 DvmDex* pDvmDex; 336 DexFile* pDexFile; 337 ArrayObject* stringArray; 338 Thread* self = dvmThreadSelf(); 339 340 if (!validateCookie(cookie)) 341 RETURN_VOID(); 342 343 if (pDexOrJar->isDex) 344 pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile); 345 else 346 pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile); 347 assert(pDvmDex != NULL); 348 pDexFile = pDvmDex->pDexFile; 349 350 int count = pDexFile->pHeader->classDefsSize; 351 stringArray = dvmAllocObjectArray(gDvm.classJavaLangString, count, 352 ALLOC_DEFAULT); 353 if (stringArray == NULL) { 354 /* probably OOM */ 355 LOGD("Failed allocating array of %d strings\n", count); 356 assert(dvmCheckException(self)); 357 RETURN_VOID(); 358 } 359 360 int i; 361 for (i = 0; i < count; i++) { 362 const DexClassDef* pClassDef = dexGetClassDef(pDexFile, i); 363 const char* descriptor = 364 dexStringByTypeIdx(pDexFile, pClassDef->classIdx); 365 366 char* className = dvmDescriptorToDot(descriptor); 367 StringObject* str = dvmCreateStringFromCstr(className); 368 dvmSetObjectArrayElement(stringArray, i, (Object *)str); 369 dvmReleaseTrackedAlloc((Object *)str, self); 370 free(className); 371 } 372 373 dvmReleaseTrackedAlloc((Object*)stringArray, self); 374 RETURN_PTR(stringArray); 375 } 376 377 /* 378 * public static boolean isDexOptNeeded(String apkName) 379 * throws FileNotFoundException, IOException 380 * 381 * Returns true if the VM believes that the apk/jar file is out of date 382 * and should be passed through "dexopt" again. 383 * 384 * @param fileName the absolute path to the apk/jar file to examine. 385 * @return true if dexopt should be called on the file, false otherwise. 386 * @throws java.io.FileNotFoundException if fileName is not readable, 387 * not a file, or not present. 388 * @throws java.io.IOException if fileName is not a valid apk/jar file or 389 * if problems occur while parsing it. 390 * @throws java.lang.NullPointerException if fileName is null. 391 * @throws dalvik.system.StaleDexCacheError if the optimized dex file 392 * is stale but exists on a read-only partition. 393 */ 394 static void Dalvik_dalvik_system_DexFile_isDexOptNeeded(const u4* args, 395 JValue* pResult) 396 { 397 StringObject* nameObj = (StringObject*) args[0]; 398 char* name; 399 DexCacheStatus status; 400 int result; 401 402 name = dvmCreateCstrFromString(nameObj); 403 if (name == NULL) { 404 dvmThrowException("Ljava/lang/NullPointerException;", NULL); 405 RETURN_VOID(); 406 } 407 if (access(name, R_OK) != 0) { 408 dvmThrowException("Ljava/io/FileNotFoundException;", name); 409 free(name); 410 RETURN_VOID(); 411 } 412 status = dvmDexCacheStatus(name); 413 LOGV("dvmDexCacheStatus(%s) returned %d\n", name, status); 414 415 result = true; 416 switch (status) { 417 default: //FALLTHROUGH 418 case DEX_CACHE_BAD_ARCHIVE: 419 dvmThrowException("Ljava/io/IOException;", name); 420 result = -1; 421 break; 422 case DEX_CACHE_OK: 423 result = false; 424 break; 425 case DEX_CACHE_STALE: 426 result = true; 427 break; 428 case DEX_CACHE_STALE_ODEX: 429 dvmThrowException("Ldalvik/system/StaleDexCacheError;", name); 430 result = -1; 431 break; 432 } 433 free(name); 434 435 if (result >= 0) { 436 RETURN_BOOLEAN(result); 437 } else { 438 RETURN_VOID(); 439 } 440 } 441 442 const DalvikNativeMethod dvm_dalvik_system_DexFile[] = { 443 { "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I", 444 Dalvik_dalvik_system_DexFile_openDexFile }, 445 { "closeDexFile", "(I)V", 446 Dalvik_dalvik_system_DexFile_closeDexFile }, 447 { "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;ILjava/security/ProtectionDomain;)Ljava/lang/Class;", 448 Dalvik_dalvik_system_DexFile_defineClass }, 449 { "getClassNameList", "(I)[Ljava/lang/String;", 450 Dalvik_dalvik_system_DexFile_getClassNameList }, 451 { "isDexOptNeeded", "(Ljava/lang/String;)Z", 452 Dalvik_dalvik_system_DexFile_isDexOptNeeded }, 453 { NULL, NULL, NULL }, 454 }; 455