DexClassLoader动态加载分析
阅读原文时间:2023年07月09日阅读:2

转载自:http://www.blogfshare.com/dexclassloader.html

看到原来有把原始的dex文件加密保存,然后解密后使用DexClassLoader加载文件的方法,就来分析下DexClassLoader的加载流程:

源码地址:http://androidxref.com/4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

该class加载器是加载包含classes.dex文件的jar文件或者apk文件,需要一个应用私有的,可写的目录去缓存优化的classes。可以用使用File dexoutputDir = context.getDir(“dex”,0);创建一个这样的目录,不要使用外部缓存,以保护你的应用被代码注入。

构造方法如下:

public class DexClassLoader extends BaseDexClassLoader {

    下面这段注释详细地说明了这个构造函数中各个参数地意义,不作阐述了,希望大家能够认真阅读,思考;

public DexClassLoader(String dexPath, String optimizedDirectory,

            String libraryPath, ClassLoader parent) {

        super(dexPath, new File(optimizedDirectory), libraryPath, parent);

    }

}

dexpath为jar或apk文件目录。

optimizedDirectory为优化dex缓存目录。

libraryPath包含native lib的目录路径。

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);

}

第一句调用的还是父类的构造函数,也就是ClassLoader的构造函数:

protected ClassLoader(ClassLoader parentLoader) {

        this(parentLoader, false);

    }

    /*

     * constructor for the BootClassLoader which needs parent to be null.

     */

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {

       if (parentLoader == null && !nullAllowed) {

            throw new NullPointerException(“parentLoader == null && !nullAllowed”);

      }

      parent = parentLoader;

}

该构造函数把传进来的父类加载器赋给了私有变量parent。

再来看

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

pathList为该类的私有成员变量,类型为DexPathList,进去DexPathList函数:

public DexPathList(ClassLoader definingContext, String dexPath,

            String libraryPath, File optimizedDirectory) {

    ………..    

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

                                           suppressedExceptions);

    ………..

}

前面是一些对于传入参数的验证,然后调用了makeDexElements。

private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,

                                             ArrayList suppressedExceptions) {

ArrayList elements = new ArrayList();

        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()) {

                elements.add(new Element(file, true, null, null));

            } else {

                System.logW(“Unknown file type for: ” + file);

            }

if ((zip != null) || (dex != null)) {

                elements.add(new Element(file, false, zip, dex));

            }

        }

return elements.toArray(new Element[elements.size()]);

    }

}

不管是dex文件,还是apk文件最终加载的都是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);

        }

}

如果optimizedDirectory为null就会调用openDexFile(fileName, null, 0);加载文件。

否则调用DexFile.loadDex(file.getPath(), optimizedPath, 0);

而这个函数也只是直接调用new DexFile(sourcePathName, outputPathName, flags);

里面调用的也是openDexFile(sourceName, outputName, flags);

所以最后都是调用openDexFile,跟进这个函数:

private static int openDexFile(String sourceName, String outputName,

        int flags) throws IOException {

        return openDexFileNative(new File(sourceName).getCanonicalPath(),

                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),

                                 flags);

}

而这个函数调用的是so的openDexFileNative这个函数。打开成功则返回一个cookie。

接下来就是分析native函数的实现部分了。

———-openDexFileNative———-

代码地址:http://androidxref.com/4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp

static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,

    JValue* pResult)

{

    ……………

if (hasDexExtension(sourceName)

            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {

        ALOGV(“Opening DEX file ‘%s’ (DEX)”, sourceName);

pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));

        pDexOrJar->isDex = true;

        pDexOrJar->pRawDexFile = pRawDexFile;

        pDexOrJar->pDexMemory = NULL;

    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {

        ALOGV(“Opening DEX file ‘%s’ (Jar)”, sourceName);

pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));

        pDexOrJar->isDex = false;

        pDexOrJar->pJarFile = pJarFile;

        pDexOrJar->pDexMemory = NULL;

    } else {

        ALOGV(“Unable to open DEX file ‘%s’”, sourceName);

        dvmThrowIOException(“unable to open DEX file”);

    }

    ……………

}

这里会根据是否为dex文件或者包含classes.dex文件的jar,分别调用函数dvmRawDexFileOpen和dvmJarFileOpen来处理,最终返回一个DexOrJar的结构。

首先来看dvmRawDexFileOpen函数的处理:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,

    RawDexFile** ppRawDexFile, bool isBootstrap)
{

    ……………..

    dexFd = open(fileName,
O_RDONLY);

    if (dexFd < 0) goto bail;

    /* If we fork/exec into dexopt, don't let it inherit
the open fd. */

    dvmSetCloseOnExec(dexFd);

    //校验前8个字节的magic是否正确,然后把校验和保存到adler32

    if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {

        ALOGE("Error
with header for %s", fileName);

        goto bail;

    }

    //得到文件修改时间以及文件大小

   if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {

        ALOGE("Error
with stat for %s", fileName);

        goto bail;

    }

    ……………..

    //调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回

    optFd = dvmOpenCachedDexFile(fileName,
cachedName, modTime,

        adler32, isBootstrap, &newFile, /*createIfMissing=*/true);

    if (optFd < 0) {

        ALOGI("Unable
to open or create cache for %s (%s)",

                fileName, cachedName);

        goto bail;

    }

    locked = true;

       //如果成功生了opt头

    if (newFile) {

        u8 startWhen, copyWhen, endWhen;

        bool result;

       off_t dexOffset;

        dexOffset = lseek(optFd, 0,
SEEK_CUR);

        result = (dexOffset > 0);

        if (result) {

            startWhen = dvmGetRelativeTimeUsec();

            // 将dex文件中的内容写入文件的当前位置,也就是从dexOffset的偏移处开始写

            result = copyFileToFile(optFd,
dexFd, fileSize) == 0;

            copyWhen = dvmGetRelativeTimeUsec();

        }

        if (result) {

            //对dex文件进行优化

            result = dvmOptimizeDexFile(optFd,
dexOffset, fileSize,

                fileName, modTime, adler32, isBootstrap);

        }

        if (!result) {

            ALOGE("Unable
to extract+optimize DEX from '%s'", fileName);

            goto bail;

        }

        endWhen = dvmGetRelativeTimeUsec();

        ALOGD("DEX
prep '%s': copy in %dms, rewrite %dms",

            fileName,

            (int) (copyWhen - startWhen) / 1000,

            (int) (endWhen - copyWhen) / 1000);

    }

     //dvmDexFileOpenFromFd这个函数最主要在这里干了两件事情

     // 1.将优化后得dex文件(也就是odex文件)通过mmap映射到内存中,并通过mprotect修改它的映射内存为只读权限

     // 2.将映射为只读的这块dex数据中的内容全部提取到DexFile这个数据结构中去

    if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {

        ALOGI("Unable
to map cached %s", fileName);

        goto bail;

    }

    if (locked) {

        /* unlock the fd */

       if (!dvmUnlockCachedDexFile(optFd)) {

            /* uh oh -- this process needs to exit or
we'll wedge the system */

            ALOGE("Unable
to unlock DEX file");

            goto bail;

        }

        locked = false;

    }

    ALOGV("Successfully
opened '%s'", fileName);

    //填充结构体 RawDexFile

    *ppRawDexFile = (RawDexFile*) calloc(1,
sizeof(RawDexFile));

    (*ppRawDexFile)->cacheFileName = cachedName;

   (*ppRawDexFile)->pDvmDex = pDvmDex;

    cachedName = NULL;  
   // don't free it below

    result = 0;

bail:

    free(cachedName);

    if (dexFd >= 0) {

        close(dexFd);

    }

    if (optFd >= 0) {

        if (locked)

            (void) dvmUnlockCachedDexFile(optFd);

        close(optFd);

    }

    return result;
}

最后成功的话,填充RawDexFile。

dvmJarFileOpen的代码处理也是差不多的。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

int dvmJarFileOpen(const char* fileName, const char* odexOutputName,

    JarFile** ppJarFile,
bool isBootstrap)
{

    …

    …

    …

    //调用函数dexZipOpenArchive来打开zip文件,并缓存到系统内存里

    if (dexZipOpenArchive(fileName, &archive) != 0)

        goto bail;

    archiveOpen = true;

    …

    //这行代码设置当执行完成后,关闭这个文件句柄

    dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));

    …

    //优先处理已经优化了的Dex文件

    fd = openAlternateSuffix(fileName, "odex",
O_RDONLY, &cachedName);

    …

    //从压缩包里找到Dex文件,然后打开这个文件

    entry = dexZipFindEntry(&archive,
kDexInJarName);

    …

    //把未经过优化的Dex文件进行优化处理,并输出到指定的文件

    if (odexOutputName == NULL) {

                cachedName = dexOptGenerateCacheFileName(fileName,

                                kDexInJarName);

    }

    …

    //创建缓存的优化文件

    fd = dvmOpenCachedDexFile(fileName,
cachedName,

                    dexGetZipEntryModTime(&archive,
entry),

                    dexGetZipEntryCrc32(&archive,
entry),

                    isBootstrap, &newFile, /*createIfMissing=*/true);

    …

    //调用函数dexZipExtractEntryToFile从压缩包里解压文件出来

    if (result) {

                    startWhen = dvmGetRelativeTimeUsec();

                    result = dexZipExtractEntryToFile(&archive,
entry, fd) == 0;

                    extractWhen = dvmGetRelativeTimeUsec();

                 }

    …

    //调用函数dvmOptimizeDexFile对Dex文件进行优化处理

    if (result) {

                    result = dvmOptimizeDexFile(fd,
dexOffset,

                                dexGetZipEntryUncompLen(&archive,
entry),

                                fileName,

                                dexGetZipEntryModTime(&archive,
entry),

                                dexGetZipEntryCrc32(&archive,
entry),

                                isBootstrap);

                }

    …

    //调用函数dvmDexFileOpenFromFd来缓存dex文件

    //并分析文件的内容。比如标记是否优化的文件,通过签名检查Dex文件是否合法

    if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {

        ALOGI("Unable
to map %s in %s", kDexInJarName, fileName);

        goto bail;

    }

    …

    //保存文件到缓存里,标记这个文件句柄已经保存到缓存

    if (locked) {

        /* unlock the fd */

        if (!dvmUnlockCachedDexFile(fd)) {

            /* uh oh -- this process needs to exit or
we'll wedge the system */

            ALOGE("Unable
to unlock DEX file");

            goto bail;

        }

        locked = false;

    }

    …

     //设置一些相关信息返回前面的函数处理。

    *ppJarFile = (JarFile*) calloc(1,
sizeof(JarFile));

    (*ppJarFile)->archive = archive;

    (*ppJarFile)->cacheFileName = cachedName;

    (*ppJarFile)->pDvmDex = pDvmDex;

    cachedName = NULL;  
   // don't free it below

    result = 0;

    …

}

最后成功的话,填充JarFile。

参考文章:

http://bbs.pediy.com/showthread.php?t=199230

http://0nly3nd.sinaapp.com/?p=688

http://blog.csdn.net/roland_sun/article/details/47183119

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章