dex檔案開啟
我們知道,要讀取一個類程式碼,或讀取類裡的方法程式碼,都需要開啟Dex檔案,然後按前面介紹的格式去分析,並且讀取出相應的內容,才可以給虛擬機器進行解釋執行。現在,我們就來學習和分析Dex檔案的讀取相關的程式碼。如下:
/*
*Open the specified file read-only. We memory-map the entire thingand
*parse the contents.
*
*This will be called on non-Zip files, especially during VM startup,so
*we don't want to be too noisy about certain types of failure. (Do
*we want a "quiet" flag?)
*
*On success, we fill out the contents of "pArchive" andreturn 0.
*/
intdexZipOpenArchive(const char* fileName, ZipArchive* pArchive)
{
上面這段程式碼輸入檔名稱,輸出檔案內容物件。
int fd, err;
LOGV("Opening archive '%s' %p\n",fileName, pArchive);
memset(pArchive, 0, sizeof(ZipArchive));
上面這行程式碼清空D
fd = open(fileName, O_RDONLY, 0);
if (fd < 0) {
err = errno ? errno : -1;
LOGV("Unable to open '%s': %s\n",fileName, strerror(err));
return err;
}
這段程式碼呼叫函式open來開啟檔案,如果不成功就輸出出錯提示。
return dexZipPrepArchive(fd, fileName,pArchive);
這行程式碼呼叫函式dexZipPrepArchive來生成dex檔案物件。
}
在上面這個函式裡,主要輸入兩個引數:
行檔案分析,並把相應內容設定給檔案物件pArchive。
下面來看一下pArchive物件的結構,如下:
typedefstruct ZipArchive {
/* open Zip archive */
int mFd;
這個zip檔案的檔案控制代碼,也就是上面呼叫open函式開啟成功後控制代碼。
/* mapped file */
MemMapping mMap;
這個是把zip檔案對映到記憶體,加快檔案讀取,提高檔案讀取效率。
/* number of entries in the Zip archive */
int mNumEntries;
這個是儲存zip檔案的入口。
/*
* We know how many entries are in the Ziparchive, so we can have a
* fixed-size hash table. We probe oncollisions.
*/
int mHashTableSize;
ZipHashEntry* mHashTable;
這裡是通過hash的方法來提高讀取檔案速度。
}ZipArchive;
ZipArchive結構儲存zip檔案的控制代碼、檔案內容對映記憶體地址、zip入口個數和入口地址(使用hash表達)。
這一段程式碼實現開啟Dex檔案,由於Dex檔案採用zip壓縮,所以需要先從zip檔案裡解壓出來,才可以恢復到Dex原始資料。
下面來分析這個函式程式碼,如下:
intdexZipPrepArchive(int fd, const char* debugFileName, ZipArchive*pArchive)
{
這個函式輸入檔案控制代碼、檔名稱、壓縮檔案物件。
MemMapping map;
int err;
map.addr = NULL;
memset(pArchive, 0, sizeof(*pArchive));
pArchive->mFd = fd;
這行程式碼是儲存檔案控制代碼。
if (sysMapFileInShmem(pArchive->mFd,&map) != 0) {
err = -1;
LOGW("Map of '%s' failed\n",debugFileName);
goto bail;
}
if (map.length < kEOCDLen) {
err = -1;
LOGV("File '%s' too small to be zip(%zd)\n", debugFileName,map.length);
goto bail;
}
這段程式碼對映檔案資料到記憶體。
if (!parseZipArchive(pArchive, &map)) {
err = -1;
LOGV("Parsing '%s' failed\n",debugFileName);
goto bail;
}
這段程式碼是分析zip檔案。
/* success */
err = 0;
sysCopyMap(&pArchive->mMap, &map);
map.addr = NULL;
這段程式碼拷貝到對映位置。
bail:
if (err != 0)
dexZipCloseArchive(pArchive);
if (map.addr != NULL)
sysReleaseShmem(&map);
return err;
}
函式dexZipPrepArchive的處理,主要就是先儲存檔案控制代碼,然後建立檔案記憶體對映,呼叫parseZipArchive函式來分析zip的所有入口點,並記錄到相應的hash表裡,最後呼叫sysCopyMap函式來儲存到zip檔案物件結構裡。
由上面分析可知,dex檔案是壓縮成zip檔案,這樣可以減少佔用空間。dex檔案在系統裡是怎麼樣開啟的過程呢?其它經過下面的過程:
1)系統初始化虛擬機器時,會初始化原始方法gDvmNativeMethodSet集合。
2)在原始方法集合裡有一個函式集合dvm_dalvik_system_DexFile,註冊它為Ldalvik/system/DexFile串,當虛擬機器呼叫DexFile相關函式時,就會呼叫這些函式來處理Dex檔案。
3)在處理Dex檔案時,會呼叫函式集合:dvm_dalvik_system_DexFile,這個函式集合裡,主要有如下函式:
constDalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{"openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFile},
{"closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile},
{"defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;ILjava/security/ProtectionDomain;)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClass},
{"getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList},
{"isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded},
{NULL, NULL, NULL },
};
openDexFile方法對應的原始函式是Dalvik_dalvik_system_DexFile_openDexFile,它是開啟Dex檔案函式。
closeDexFile方法對應的原始函式是Dalvik_dalvik_system_DexFile_closeDexFile,它是關閉已經開啟的Dex檔案函式。
4)在Dalvik_dalvik_system_DexFile_openDexFile函式裡,呼叫函式dvmJarFileOpen開啟JAR或者ZIP壓縮的檔案。
5)在dvmJarFileOpen函式裡,呼叫dexZipOpenArchive來處理ZIP檔案,呼叫dexZipFindEntry函式讀取ZIP解壓的檔案,呼叫dvmDexFileOpenFromFd函式讀取相應的類資料到記憶體,並返回給虛擬機器。
從上面可知呼叫函式Dalvik_dalvik_system_DexFile_openDexFile來開啟Dex檔案,這個函式的原始碼如下:
staticvoid Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
JValue* pResult)
{
StringObject* sourceNameObj =(StringObject*) args[0];
這行是輸入的Jar或Dex檔名引數。
StringObject* outputNameObj =(StringObject*) args[1];
這行是輸出的檔名引數。
int flags = args[2];
這行是處理的標示。
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
if (sourceNameObj == NULL) {
dvmThrowException("Ljava/lang/NullPointerException;",NULL);
RETURN_VOID();
}
這段程式碼是當輸入檔名稱為空物件時,就丟擲異常。
sourceName =dvmCreateCstrFromString(sourceNameObj);
這行程式碼呼叫函式dvmCreateCstrFromString把java字串轉換C字串,由於Java物件表示的字串並不能立即就使用到C語言裡,所以需要轉換才能使用。
if (outputNameObj != NULL)
outputName =dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
這段程式碼是同樣把輸出字串轉換C字串。
/*
* We have to deal with the possibility thatsomebody might try to
* open one of our bootstrap class DEXfiles. The set of dependencies
* will be different, and hence the resultsof optimization might be
* different, which means we'd actually needto have two versions of
* the optimized DEX: one that only knowsabout part of the boot class
* path, and one that knows about everythingin it. The latter might
* optimize field/method accesses based on aclass that appeared later
* in the class path.
*
* We can't let the user-defined classloader open it and start using
* the classes, since the optimized form ofthe code skips some of
* the method and field resolution that wewould ordinarily do, and
* we'd have the wrong semantics.
*
* We have to reject attempts to manuallyopen a DEX file from the boot
* class path. The easiest way to do thisis by filename, which works
* out because variations in name (e.g."/system/framework/./ext.jar")
* result in us hitting a differentdalvik-cache entry. It's also fine
* if the caller specifies their own outputfile.
*/
if(dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
LOGW("Refusing to reopen boot DEX'%s'\n", sourceName);
dvmThrowException("Ljava/io/IOException;",
"Re-opening BOOTCLASSPATH DEXfiles is not allowed");
free(sourceName);
RETURN_VOID();
}
這段程式碼是判斷使用者是否載入系統目錄下面的Dex檔案,如果載入就要拒絕這樣的操作,因為系統啟動時已經載入了一份這樣的優化程式碼,沒有必要再次載入一次。
/*
* Try to open it directly as a DEX. Ifthat fails, try it as a Zip
* with a "classes.dex" inside.
*/
if (dvmRawDexFileOpen(sourceName,outputName, &pRawDexFile, false) == 0) {
LOGV("Opening DEX file '%s'(DEX)\n", sourceName);
pDexOrJar = (DexOrJar*)malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
這段程式碼是嘗試載入Dex檔案,但基本不存在直接加Dex檔案的情況,因此在函式dvmRawDexFileOpen還是空函式,沒有實際的內容。
}else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false)== 0) {
LOGV("Opening DEX file '%s'(Jar)\n", sourceName);
pDexOrJar = (DexOrJar*)malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
這段程式碼是載入Jar檔案,就是從這裡載入Dex檔案到快取裡。
}else {
LOGV("Unable to open DEX file'%s'\n", sourceName);
dvmThrowException("Ljava/io/IOException;","unable to open DEX file");
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
這行程式碼儲存檔名稱到Dex檔案物件裡。
/* add to hash table */
u4 hash = dvmComputeUtf8Hash(sourceName);
這行程式碼通過檔名稱計算HASH串,加速對檔案的查詢速度。
void* result;
dvmHashTableLock(gDvm.userDexFiles);
result =dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar,
hashcmpDexOrJar, true);
dvmHashTableUnlock(gDvm.userDexFiles);
這段程式碼新增HASH表裡,以便後面查詢使用。
if (result != pDexOrJar) {
LOGE("Pointer has already beenadded?\n");
dvmAbort();
}
pDexOrJar->okayToFree = true;
}else
free(sourceName);
RETURN_PTR(pDexOrJar);
這行程式碼返回開啟的檔案物件。
}
這個函式是通過JAVA呼叫時輸入Dex檔名稱,然後載入Dex檔案,最後把這個檔名稱放到HASH表裡,然後返回開啟的物件。
在上面的函式裡,提到使用dvmJarFileOpen函式找到classes.dex檔案,並載入到記憶體裡,然後提供後面的函式使用。現在就來分析這個函式的程式碼,如下:
intdvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
在這裡提供四個引數,第一個引數fileName是輸入的Jar的檔名稱;第二個引數odexOutputName是進行優化後的Dex輸出檔案;第三個引數ppJarFile是已經開啟並快取到記憶體裡的檔案物件;第四個引數isBootstrap是指示是否系統裡Dex檔案。
ZipArchive archive;
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
bool archiveOpen = false;
bool locked = false;
int fd = -1;
int result = -1;
/* Even if we're not going to look at thearchive, we need to
* open it so we can stuff it intoppJarFile.
*/
if (dexZipOpenArchive(fileName, &archive)!= 0)
goto bail;
archiveOpen = true;
這段程式碼是呼叫前面介紹的函式dexZipOpenArchive來開啟Zip檔案,並快取到系統記憶體裡。
/* If we fork/exec into dexopt, don't letit inherit the archive's fd.
*/
dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
這行程式碼設定當執行完成後,關閉這個檔案控制代碼。
/* First, look for a ".odex"alongside the jar file. It will
* have the same name/path except for theextension.
*/
fd = openAlternateSuffix(fileName, "odex",O_RDONLY, &cachedName);
if (fd >= 0) {
這裡優先處理優化的Dex檔案。
LOGV("Using alternate file (odex)for %s ...\n", fileName);
if (!dvmCheckOptHeaderAndDependencies(fd,false, 0, 0, true, true)) {
LOGE("%s odex has staledependencies\n", fileName);
free(cachedName);
close(fd);
fd = -1;
goto tryArchive;
} else {
LOGV("%s odex has gooddependencies\n", fileName);
//TODO: make sure that the .odexactually corresponds
// to the classes.dex inside thearchive (if present).
// For typical use there will beno classes.dex.
}
}else {
ZipEntry entry;
tryArchive:
/*
* Pre-created .odex absent or stale. Look inside the jar for a
* "classes.dex".
*/
entry = dexZipFindEntry(&archive,kDexInJarName);
這行程式碼是從壓縮包裡找到Dex檔案,然後開啟這個檔案。
if (entry != NULL) {
bool newFile = false;
/*
* We've found the one we want. Seeif there's an up-to-date copy
* in the cache.
*
* On return, "fd" will beseeked just past the "opt" header.
*
* If a stale .odex file is presentand classes.dex exists in
* the archive, this will *not*return an fd pointing to the
* .odex file; the fd will point intodalvik-cache like any
* other jar.
*/
if (odexOutputName == NULL) {
cachedName =dexOptGenerateCacheFileName(fileName,
kDexInJarName);
if (cachedName == NULL)
goto bail;
這段程式碼是把Dex檔案進行優化處理,並輸出到指定的檔案。
} else {
cachedName =strdup(odexOutputName);
}
LOGV("dvmDexCacheStatus:Checking cache for %s (%s)\n",
fileName, cachedName);
fd = dvmOpenCachedDexFile(fileName,cachedName,
dexGetZipEntryModTime(&archive,entry),
dexGetZipEntryCrc32(&archive,entry),
isBootstrap, &newFile,/*createIfMissing=*/true);
這段程式碼建立快取的優化檔案。
if (fd < 0) {
LOGI("Unable to open orcreate cache for %s (%s)\n",
fileName, cachedName);
goto bail;
}
locked = true;
/*
* If fd points to a new file(because there was no cached version,
* or the cached version was stale),generate the optimized DEX.
* The file descriptor returned isstill locked, and is positioned
* just past the optimization header.
*/
if (newFile) {
u8 startWhen, extractWhen,endWhen;
bool result;
off_t dexOffset, fileLen;
dexOffset = lseek(fd, 0,SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen =dvmGetRelativeTimeUsec();
result =dexZipExtractEntryToFile(&archive, entry, fd);
extractWhen =dvmGetRelativeTimeUsec();
這段程式碼呼叫函式dexZipExtractEntryToFile從壓縮包裡解壓檔案出來。
}
if (result) {
result =dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive,entry),
fileName,
dexGetZipEntryModTime(&archive,entry),
dexGetZipEntryCrc32(&archive,entry),
isBootstrap);
這段程式碼呼叫函式dvmOptimizeDexFile對Dex檔案進行優化處理。
}
if (!result) {
LOGE("Unable toextract+optimize DEX from '%s'\n",
fileName);
goto bail;
}
endWhen =dvmGetRelativeTimeUsec();
LOGD("DEX prep '%s': unzipin %dms, rewrite %dms\n",
fileName,
(int) (extractWhen -startWhen) / 1000,
(int) (endWhen - extractWhen)/ 1000);
}
} else {
LOGI("Zip is good, but no %sinside, and no valid .odex "
"file in the samedirectory\n", kDexInJarName);
goto bail;
}
}
/*
* Map the cached version. This immediatelyrewinds the fd, so it
* doesn't have to be seeked anywhere inparticular.
*/
if (dvmDexFileOpenFromFd(fd, &pDvmDex)!= 0) {
LOGI("Unable to map %s in %s\n",kDexInJarName, fileName);
goto bail;
}
這段程式碼是呼叫函式dvmDexFileOpenFromFd來快取Dex檔案,並分析檔案的內容。比如標記是否優化的檔案,通過簽名檢查Dex檔案是否合法。
if (locked) {