1. 程式人生 > >dex檔案開啟

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

ex檔案物件。

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檔案物件。

}

在上面這個函式裡,主要輸入兩個引數:

fileNamepArchive。其中fileName是輸入要開啟的dex檔名稱,當然它是包括檔案路徑的;pArchive是開啟這個檔案後用來表達dex檔案內容的物件。程式碼具體過程是先呼叫函式memset來清空pArchive檔案物件,然後呼叫函式open開啟檔案,把檔案控制代碼傳送給函式dexZipPrepArchive

行檔案分析,並把相應內容設定給檔案物件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);

這行程式碼呼叫函式dvmCreateCstrFromStringjava字串轉換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) {