Android .dex文件簡(jiǎn)介

1.引言

上節(jié)我問過自己?jiǎn)栴}dvm,將Class轉(zhuǎn)成成.dex文件,然后再將.dex文件轉(zhuǎn)換成Class文件。那么為什么要這樣做。這樣做不是多此一舉嗎,為什么不直接用Class文件,這樣做豈不是很方面嗎?解決這些問題之前我首要的是要明白什么是.dex,內(nèi)部結(jié)構(gòu),有什么用。接下來我將談?wù)?dex。主要參考著幾篇優(yōu)秀的文章。Android中dex文件的加載與優(yōu)化流程 。
分析的參考博客.dex淺析在此感謝他們的付出。

2.正題

2.1 .dex的結(jié)構(gòu)

.dex結(jié)構(gòu)分成三部分:
文件頭:表明了是dex文件,已經(jīng)文件的大小等等數(shù)據(jù)
索引頭:如下圖所示
數(shù)據(jù)區(qū):數(shù)據(jù)區(qū),就像是jvm中的堆保存方法+變量。(寫在這對(duì)jvm的常量池,堆棧,寄存器不是很清楚,準(zhǔn)備專門在寫一篇文章記錄下加強(qiáng)自己的記憶)

Paste_Image.png

2.2 odex文件

odex是OptimizedDEX的縮寫,表示對(duì)dex的優(yōu)化。寫到這讓我想起上一篇DexClassLoad的構(gòu)造方法.里面需要傳入一個(gè)OptimizedPath。這個(gè)OptimizedPath大概就是這個(gè)odex的路徑。

2.3 DexFile文件

DexFile是dex文件被映射到內(nèi)存中的結(jié)構(gòu),出了基本的dex文件結(jié)構(gòu)外,還包含了DexOptHead和尾部附加的數(shù)據(jù),這些數(shù)據(jù)是Android系統(tǒng)為了結(jié)合當(dāng)前平臺(tái)特性對(duì)dex文件的結(jié)構(gòu)進(jìn)行了優(yōu)化和擴(kuò)充,是運(yùn)行效率更高。

Paste_Image.png

從上面的圖可以看出。DexFile 保存了一個(gè)Class對(duì)象的屬性+方法的索引。也就是說 我們可以根據(jù)一個(gè)DexFile 得到一個(gè)Class對(duì)象。

2.4 .dex的加載過程

**加載過程略微的繁瑣,了解下過程就像。我直接復(fù)制過來的 **
Android提供了一個(gè)專門驗(yàn)證與優(yōu)化dex文件的工具dexopt。其源碼位于Android系統(tǒng)源碼的dalvik/dexopt目錄下,Dalvik虛擬機(jī)在加載一個(gè)dex文件時(shí),通過指定的驗(yàn)證與優(yōu)化選項(xiàng)來調(diào)用dexopt進(jìn)行相應(yīng)的驗(yàn)證與優(yōu)化操作。
dexopt的主程序?yàn)镺ptMain.cpp,其中處理apk/jar/zip文件中的classes.dex的函數(shù)為extractAndProcessZip(),extractAndProcessZip()首先通過dexZipFindEntry()函數(shù)檢查目標(biāo)文件中是否擁有class.dex,如果沒有就失敗返回,成功的話就調(diào)用dexZipGetEntryInfo()函數(shù)來讀取classes.dex的時(shí)間戳與crc校驗(yàn)值,如果這一步?jīng)]有問題,接著調(diào)用dexZipExtractEntryTo-File()函數(shù)釋放classes.dex為緩存文件,然后開始解析傳遞過來的驗(yàn)證與優(yōu)化選項(xiàng),驗(yàn)證選項(xiàng)使用“v=”指出,優(yōu)化選項(xiàng)使用“o=”指出。所有的預(yù)備工作都做完后,調(diào)用dvmPrepForDexOpt()函數(shù)啟動(dòng)一個(gè)虛擬機(jī)進(jìn)程,在這個(gè)函數(shù)中,優(yōu)化選項(xiàng)dexOptMode與驗(yàn)證選項(xiàng)varifyMode被傳遞到了全局DvmGlobals結(jié)構(gòu)gDvm的dexOptMode與classVerifyMode字段中。這時(shí)候所有的初始化工作已經(jīng)完成,dexopt調(diào)用dvmContinueOptimization()函數(shù)開始真正的驗(yàn)證和優(yōu)化工作。
dvmContinueOptimization()函數(shù)的調(diào)用鏈比較長(zhǎng)。首先從OptMain.cpp轉(zhuǎn)移到、dalvik/vm/analysis/DexPrepare.cpp,因?yàn)檫@里有dvmContinueOptimization()函數(shù)的實(shí)現(xiàn)。函數(shù)首先對(duì)dex文件做簡(jiǎn)單的檢查,確保傳遞進(jìn)來的目標(biāo)文件屬于dex或odex,接著調(diào)用mmap()函數(shù)將整個(gè)文件映射到內(nèi)存中,然后根據(jù)gDvm的dexOptMode與classVerifyMode字段來設(shè)置doVarify與doOpt兩個(gè)布爾值,接著調(diào)用rewriteDex()函數(shù)來重寫dex文件,這里的重寫內(nèi)容包括字符調(diào)整、結(jié)構(gòu)重新對(duì)齊、類驗(yàn)證信息以及輔助數(shù)據(jù)。rewriteDex()函數(shù)調(diào)用dexSwapAndVerify()調(diào)整字節(jié)序,接著調(diào)用dvmDexFileOpenPartial()創(chuàng)建DexFile結(jié)構(gòu),dvmDexFileOpenPartial()函數(shù)的實(shí)現(xiàn)在Android系統(tǒng)源碼dalvik/vm/DvmDex.cpp文件中,該函數(shù)調(diào)用dexFileParse()函數(shù)解析dex文件,dexFileParse()函數(shù)讀取dex文件的頭部,并根據(jù)需要調(diào)用驗(yàn)證dexComputeChecksum()函數(shù)或調(diào)用dexComputeOptChecksum()函數(shù)來驗(yàn)證dex或odex文件愛你頭的checksum與signature字段。
接著回到DvmDex.cpp文件繼續(xù)看代碼,當(dāng)驗(yàn)證成功后,dvmDexFileOpenPartial()函數(shù)調(diào)用allocateAuxStructures()函數(shù)設(shè)置DexFile結(jié)構(gòu)輔助數(shù)據(jù)的相關(guān)字段,最后執(zhí)行完后返回到rewriteDex()函數(shù)。rewriteDex()接下來調(diào)用loadAllClasses()加載dex文件中所有的類,如果這一步失敗了,程序等不到后面的優(yōu)化與驗(yàn)證就退出了,如果沒有錯(cuò)誤發(fā)生,會(huì)調(diào)用verifyAndOptimizeClasses()函數(shù)進(jìn)行真正的驗(yàn)證工作,這個(gè)函數(shù)會(huì)調(diào)用verifyAndOptimizeClass()函數(shù)來優(yōu)化與驗(yàn)證具體的類,而verifyAndOptimizeClass()函數(shù)會(huì)細(xì)分這些工作,調(diào)用dvmVerifyClass()函數(shù)進(jìn)行驗(yàn)證,再調(diào)用dvmOptimizeClass()函數(shù)進(jìn)行優(yōu)化。
dvmVerifyClass()函數(shù)的實(shí)現(xiàn)代碼位于Android系統(tǒng)源碼的dalvik/vm/analysis/DexVerify.cpp文件中。這個(gè)函數(shù)調(diào)用verifyMethod()函數(shù)對(duì)類的所有直接方法與虛方法進(jìn)行驗(yàn)證,verifyMethod()函數(shù)具體的工作是先調(diào)用verifyInstructions()函數(shù)來驗(yàn)證方法中的指令及其數(shù)據(jù)的正確性,再調(diào)用dvmVerifyCodeFlow()函數(shù)來驗(yàn)證代碼流的正確性。
dvmOptimizeClass()函數(shù)的實(shí)現(xiàn)代碼位于Android系統(tǒng)源碼的dalvik/vm/analysis/Optimize.cpp文件愛你中。這個(gè)函數(shù)調(diào)用optimizeMethod()函數(shù)對(duì)類的所有直接方法與虛方法進(jìn)行優(yōu)化,優(yōu)化的主要工作是進(jìn)行“指令替換”,替換原則的優(yōu)先級(jí)為“volatile”替換-正確性替換-高性能替換。比如指令iget-wide會(huì)根據(jù)優(yōu)先級(jí)替換為“volatile”形式的iget-wide-volatile,而不是高性能的iget-wide-quick.
rewriteDex函數(shù)返回后,會(huì)再次調(diào)用dvmDexFileOpenPartial()來驗(yàn)證odex文件,接著調(diào)用dvmGenerateRegisterMaps()函數(shù)來填充輔助數(shù)據(jù)區(qū)結(jié)構(gòu),填充結(jié)構(gòu)完成后,接下來調(diào)用updateChecksum()函數(shù)重寫dex文件愛你的checksum值,再往下就是writeDependencies()與writeOptData()了。

流程圖:

Paste_Image.png

Paste_Image.png
Paste_Image.png

3.0 源碼分析

結(jié)合上面的概念。對(duì)DexFile等有所了解?,F(xiàn)在我結(jié)合一個(gè)大神的博客,將源碼分析也貼出來吧。個(gè)人覺得他的源碼分析很貼切,很有條理。

DexClassLoader的源碼

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

**BaseDexClassLoader **

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

接下來看DexPathList:

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
    //省略參數(shù)校驗(yàn)以及異常處理的代碼
        this.definingContext = definingContext;
        ……
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
        ……
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}

我們繼續(xù)閱讀DexPathList.java文件中makeDexElements 的關(guān)鍵代碼:

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
     // ……
        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();
            if (name.endsWith(DEX_SUFFIX)) {   //.dex文件
                // Raw dex file (not inside a zip/jar).
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                    //.apk  .jar  .zip文件
                zip = file;
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    suppressedExceptions.add(suppressed);
                }
            } else if (file.isDirectory()) {
                // We support directories for looking up resources.
                // This is only useful for running libcore tests.
                elements.add(new Element(file, true, null, null));
            } else {
                System.logW("Unknown file type for: " + file);
            }
        }
                    //……
        return elements.toArray(new Element[elements.size()]);
    }

從上面的代碼可以看出ArrayList<File> files。就是我們所有的dex文件。根絕后綴判斷文件的類型。然后調(diào)用loadDexFile。得到一個(gè)DexFile。前面說到了DexFile 是包含了一個(gè)Class類里面屬性+方法的索引。然后根據(jù)new Element(file, true, null, null) 得到一個(gè)Element,添加到了elements中。

接下來看下loadDexFile:

private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }


//生成odex的目錄
private static String optimizedPathFor(File path,
            File optimizedDirectory) {
        String fileName = path.getName();
        if (!fileName.endsWith(DEX_SUFFIX)) {
            int lastDot = fileName.lastIndexOf(".");
            if (lastDot < 0) {
                fileName += DEX_SUFFIX;
            } else {
                StringBuilder sb = new StringBuilder(lastDot + 4);
                sb.append(fileName, 0, lastDot);
                sb.append(DEX_SUFFIX);
                fileName = sb.toString();
            }
        }
        File result = new File(optimizedDirectory, fileName);
        return result.getPath();
    }

optimizedPathFor 方法:就是將dex 優(yōu)化形成optimizedDex文件。然后
通過DexFile的靜態(tài)方法將.dex文件。或者apk,.jar加載成DexFile文件。DexFile 是C語(yǔ)言中的結(jié)構(gòu)體,里面保存的都是一些指針。經(jīng)過不斷的校檢,最后通過dvm里面的c代碼處理,得到DexFile。

總結(jié):
我們可以簡(jiǎn)要總結(jié)下整個(gè)的加載流程,首先是對(duì)文件名的修正,后綴名置為”.dex”作為輸出文件,然后生個(gè)一個(gè)DexPathList對(duì)象函數(shù)直接返回一個(gè)DexPathList對(duì)象,

在DexPathList的構(gòu)造函數(shù)中調(diào)用makeDexElements()函數(shù),在makeDexElement()函數(shù)中調(diào)用loadDexFile()開始對(duì).dex或者是.jar .zip .apk文件進(jìn)行處理,

跟入loadDexFile()函數(shù)中,會(huì)發(fā)現(xiàn)里面做的工作很簡(jiǎn)單,調(diào)用optimizedPathFor()函數(shù)對(duì)optimizedDiretcory路徑進(jìn)行修正。

之后才真正通過DexFile.loadDex()開始加載文件中的數(shù)據(jù),其中的加載也只是返回一個(gè)DexFile對(duì)象。

在DexFile類的構(gòu)造函數(shù)中,重點(diǎn)便放在了其調(diào)用的openDexFile()函數(shù),在openDexFile()中調(diào)用了openDexFileNative()真正進(jìn)入native層,

在openDexFileNative()的真正實(shí)現(xiàn)中,對(duì)于后綴名為.dex的文件或者其他文件(.jar .apk .zip)分開進(jìn)行處理:

.dex文件調(diào)用dvmRawDexFileOpen();
其他文件調(diào)用dvmJarFileOpen()。

在dvmRawDexFileOpen()函數(shù)中,檢驗(yàn)dex文件的標(biāo)志,檢驗(yàn)odex文件的緩存名稱,之后將dex文件拷貝到odex文件中,并對(duì)odex進(jìn)行優(yōu)化

調(diào)用dvmDexFileOpenFromFd()對(duì)優(yōu)化后的odex文件進(jìn)行映射,通過mprotect置為"只讀"屬性并將映射的內(nèi)存結(jié)構(gòu)保存在DvmDex*結(jié)構(gòu)中。

dvmJarFileOpen()先對(duì)文件進(jìn)行映射,結(jié)構(gòu)保存在ZipArchive中,然后再嘗試以文件名作為dex文件名來“打開”文件,
如果失敗,則調(diào)用dexZipFindEntry在ZipArchive的名稱hash表中找名為"class.dex"的文件,然后創(chuàng)建odex文件,下面就和
dvmRawDexFileOpen()一樣了,就是對(duì)dex文件進(jìn)行優(yōu)化和映射。

也只是分析了一個(gè)大概流程,還有很多有待之后進(jìn)行深入。而這里對(duì)于閱讀Android源碼,有了新的體會(huì),首先是工具上,我之前一直是用Source InSight 但是對(duì)于一些函數(shù)的實(shí)現(xiàn),找起來卻是不太方便,因?yàn)楸仨氁獙⒑瘮?shù)實(shí)現(xiàn)的文件導(dǎo)入到工程中,而用VS來閱讀源碼,利用Ctrl+Shift+F的功能,在Android源碼目錄下搜索更為方便,然后可以在Source InSight中進(jìn)行導(dǎo)入,閱讀。其次不得不說閱讀源碼真的是一個(gè)比較痛苦的過程,但真的學(xué)習(xí)下來,收獲還是很大的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容