Dalvik虚拟机学习2——类加载器的创建

来源:互联网 发布:思迅软件费用 编辑:程序博客网 时间:2024/06/05 15:42

1,DexClassLoader和PathClassLoader

在android中,类文件的加载主要是通过这两个类加载器来实现的。其构造函数分别如下所示

public DexClassLoader(String dexPath, String optimizedDirectory,        String libraryPath, ClassLoader parent) {    super(dexPath, new File(optimizedDirectory), libraryPath, parent);}public PathClassLoader(String dexPath, String libraryPath,        ClassLoader parent) {    super(dexPath, null, libraryPath, parent);}

其中DexClassLoader比PathClassLoader多了一个参数,即optimizedDirectory,这个参数代表着dex文件的缓存目录,即经过优化后的odex文件(ART模式下是oat)存储目录。PathClassLoader一般加载系统类或者作为应用的默认类加载器。

2,BaseDexClassLoader

DexClassLoader和PathClassLoader都继承自BaseDexClassLoader,如1所示,DexClassLoader和PathClassLoader的构造函数都是直接调用BaseDexClassLoader,其构造函数如下

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

可以看到,在BaseDexClassLoader中主要是创建了一个DexPathList的对象。

3,DexPathList

其构造函数如下

public DexPathList(ClassLoader definingContext, String dexPath,        String libraryPath, File optimizedDirectory) {    if (definingContext == null) {        throw new NullPointerException("definingContext == null");    }    if (dexPath == null) {        throw new NullPointerException("dexPath == null");    }    if (optimizedDirectory != null) {        if (!optimizedDirectory.exists())  {            throw new IllegalArgumentException(                    "optimizedDirectory doesn't exist: "                    + optimizedDirectory);        }        if (!(optimizedDirectory.canRead()                        && optimizedDirectory.canWrite())) {            throw new IllegalArgumentException(                    "optimizedDirectory not readable/writable: "                    + optimizedDirectory);        }    }    this.definingContext = definingContext;    ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,                                       suppressedExceptions);    if (suppressedExceptions.size() > 0) {        this.dexElementsSuppressedExceptions =            suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);    } else {        dexElementsSuppressedExceptions = null;    }    this.nativeLibraryDirectories = splitLibraryPath(libraryPath);}

简单分析一下,首先对dex的路径做一下判断,如果是空的,就跑出异常。再对缓存目录检测一下,如果接受到的缓存目录不空,就看一下这个目录实际上存不存在,可不可以读,如果不可以就抛异常。接下来有一行代码

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

这行代码调用makeDexElements函数,它创建了一个dex路径的元素数组,看一下这个函数

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,                                         ArrayList<IOException> suppressedExceptions) {    ArrayList<Element> elements = new ArrayList<Element>();    /*     * Open all files and load the (direct or contained) dex files     * up front.     */    for (File file : files) {        File zip = null;        DexFile dex = null;        String name = file.getName();        if (name.endsWith(DEX_SUFFIX)) {            // 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)) {            zip = file;            try {                dex = loadDexFile(file, optimizedDirectory);            } catch (IOException suppressed) {                /*                 * IOException might get thrown "legitimately" by the DexFile constructor if the                 * zip file turns out to be resource-only (that is, no classes.dex file in it).                 * Let dex == null and hang on to the exception to add to the tea-leaves for                 * when findClass returns null.                 */                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);        }        if ((zip != null) || (dex != null)) {            elements.add(new Element(file, false, zip, dex));        }    }    return elements.toArray(new Element[elements.size()]);} 

这个函数会对传入的每个文件做出处理,因为包含dex的可能是一个单纯的以.dex结尾的文件,或者是一个.apk文件,或者是一个.jar文件,或者是一个.zip文件,对这些文件分别提取去dex文件来做处理,这里只说一下对.dex文件的处理。

      if (name.endsWith(DEX_SUFFIX)) {            // 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);            }        }

可以看到它调用了loadDexFile函数来对dex文件作出处理,并返回一个DexFile的对象。

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

可以看到,loadDexFile函数就是单纯的创建了一个DexFile的实例对象。如果缓存目录是空,则直接以传入的dex文件为参数创建DexFile,否则调用DexFile的loadDex函数创建一个DexFile对象。DexFile的loadDex只是为DexFile的构造函数设置了一些路径参数,然后调用DexFile的构造函数创建一个DexFile对象。

4,DexFile

DexFile一共三个构造函数,分别如下所示

public DexFile(File file) throws IOException {    this(file.getPath());}public DexFile(String fileName) throws IOException {    mCookie = openDexFile(fileName, null, 0);    mFileName = fileName;    guard.open("close");    //System.out.println("DEX FILE cookie is " + mCookie);}private DexFile(String sourceName, String outputName, int flags) throws IOException {    if (outputName != null) {        try {            String parent = new File(outputName).getParent();            if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {                throw new IllegalArgumentException("Optimized data directory " + parent                        + " is not owned by the current user. Shared storage cannot protect"                        + " your application from code injection attacks.");            }        } catch (ErrnoException ignored) {            // assume we'll fail with a more contextual error later        }    }    mCookie = openDexFile(sourceName, outputName, flags);    mFileName = sourceName;    guard.open("close");    //System.out.println("DEX FILE cookie is " + mCookie);}

第一个构造函数调用了第二个构造函数,所以总共两类构造函数。一个是接受单个dex路径的,另外一个接受dex路径和缓存目录的,这也对应上面提到的PathClassLoader和DexClassLoader。简单分析一下这两类构造函数,可以看到它们都调用了openDexFile函数。分别是:

mCookie = openDexFile(fileName, null, 0);mCookie = openDexFile(sourceName, outputName, flags);

openDexFile函数会调用一个native函数openDexFileNative来执行真正的dex文件加载,并会返回一个虚拟机cookie值,通过这个cookie就可以找到被加载的dex。

5,DalvikdalviksystemDexFileopenDexFileNative

第四步中提到会调用native的openDexFileNative来执行真正的dex文件加载,这个函数就对应着DalvikdalviksystemDexFileopenDexFileNative。其主要源码如下

static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,JValue* pResult)  {    StringObject* sourceNameObj = (StringObject*) args[0];    StringObject* outputNameObj = (StringObject*) args[1];    DexOrJar* pDexOrJar = NULL;    JarFile* pJarFile;    RawDexFile* pRawDexFile;    char* sourceName;    char* outputName;    if (sourceNameObj == NULL) {        dvmThrowNullPointerException("sourceName == null");        RETURN_VOID();    }    sourceName = dvmCreateCstrFromString(sourceNameObj);    ……    /*     * Try to open it directly as a DEX if the name ends with ".dex".     * If that fails (or isn't tried in the first place), try it as a     * Zip with a "classes.dex" inside.     */    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");    }    if (pDexOrJar != NULL) {        pDexOrJar->fileName = sourceName;        addToDexFileTable(pDexOrJar);    } else {        free(sourceName);    }    free(outputName);    RETURN_PTR(pDexOrJar);

}

上面的代码根据sourceName的不同(是.dex文件还是.jar文件等)来做处理,原理是一样的。下面来分析一下是dex文件的情形,首先是调用dvmRawDexFileOpen函数来直接打开dex文件,打开后会将相应数据结构存放到RawDexFile中。然后动态创建一下DexOrJar对象,并将一些关于dex的信息,尤其是RawDexFile的对象存放进去。最后再通过addToDexFileTable函数将这个DexOrJar对象存放到哈希表中。接下来首先分析一下dvmRawDexFileOpen函数的实现,再分析一下addToDexFileTable的实现。

6,dvmRawDexFileOpen

其源码如下所示:

int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,RawDexFile** ppRawDexFile, bool isBootstrap){    DvmDex* pDvmDex = NULL;    char* cachedName = NULL;    int result = -1;    int dexFd = -1;    int optFd = -1;    u4 modTime = 0;    u4 adler32 = 0;    size_t fileSize = 0;    bool newFile = false;    bool locked = false;    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);    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;    }    /*     * See if the cached file matches. If so, optFd will become a reference     * to the cached file and will have been seeked to just past the "opt"     * header.     */    if (odexOutputName == NULL) {        cachedName = dexOptGenerateCacheFileName(fileName, NULL);        if (cachedName == NULL)            goto bail;    } else {        cachedName = strdup(odexOutputName);    }    ALOGV("dvmRawDexFileOpen: Checking cache for %s (%s)",            fileName, cachedName);    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;    /*     * If optFd 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 is still locked,     * and is positioned just past the optimization header.     */    if (newFile) {        u8 startWhen, copyWhen, endWhen;        bool result;        off_t dexOffset;        dexOffset = lseek(optFd, 0, SEEK_CUR);        result = (dexOffset > 0);        if (result) {            startWhen = dvmGetRelativeTimeUsec();            result = copyFileToFile(optFd, dexFd, fileSize) == 0;            copyWhen = dvmGetRelativeTimeUsec();        }        if (result) {            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);    }    /*     * Map the cached version.  This immediately rewinds the fd, so it     * doesn't have to be seeked anywhere in particular.     */    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);    *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;}

简单分析一下这段代码:

6.1 首先打开dex文件并获取对应的文件描述符。

6.2 然后调用 verifyMagicAndGetAdler32函数验证dex文件的magic头,并且获取保存在dex文件头部的checksum,验证方法很简单,就是进行简单的头部字符串比较,读取checksum注意是按照默认的小端模式进行相应的移位 。

6.3 接着调用getModTimeAndSize获取dex文件的时间戳(上次修改时间)和文件大小(对应的文件字节数)

6.4 判断传入的odexOutputName参数(即缓存目录)是否为空,如果为空,就调用dexOptGenerateCacheFileName将其缓存文件命名为/data/dalvik-cache/@..@格式的文件(把dex路径中的/换位@)。

6.5 调用dvmOpenCachedDexFile函数创建一个缓存文件(Dalvik下是odex文件),并返回一个文件描述符。如果缓存文件不存在或者过时,就会创建一个新的。然后在这个新文件中填充一些空的opt头部信息(调用dexOptCreateEmptyHeader函数),填充信息时会填充一个真实的dexOffset信息,其它field暂时无效,都填充为1。如果缓存文件已经存在,就会对这个文件做一些校验,主要是验证一个头部信息和依赖信息。

6.6 如果6,5步创建了一个新的缓存文件,就接着往这个文件里填充信息。首先调用copyFileToFile往odex文件中填充整个dex文件数据。然后调用dvmOptimizeDexFile,而这个函数又会新创建一个进程并调用dexopt程序来进行dex优化工作,后面再详细分析dexopt的优化流程。

6.7 调用dvmDexFileOpenFromFd函数来将odex文件映射到内存中,并解析其内容。首先调用sysMapFileInShmemWritableReadOnly来将odex映射到内存。然后调用dexFileParse来对odex文件做一下校验,如checksum等信息,并将内存中的odex信息填充到一个DexFile对象中,主要是一个field信息的指针,具体如下。

pDexFile->baseAddr = data;pDexFile->pHeader = pHeader;pDexFile->pStringIds = (const DexStringId*) (data + pHeader->stringIdsOff);pDexFile->pTypeIds = (const DexTypeId*) (data + pHeader->typeIdsOff);pDexFile->pFieldIds = (const DexFieldId*) (data + pHeader->fieldIdsOff);pDexFile->pMethodIds = (const DexMethodId*) (data + pHeader->methodIdsOff);pDexFile->pProtoIds = (const DexProtoId*) (data + pHeader->protoIdsOff);pDexFile->pClassDefs = (const DexClassDef*) (data + pHeader->classDefsOff);pDexFile->pLinkData = (const DexLink*) (data + pHeader->linkOff);

然后调用allocateAuxStructures来创建一个辅助结构。首先是申请一段内存空间,在这个空间中存放着一个DvmDex对象和一些索引信息。DvmDex中存放的是在这段内存空间中相应的field索引区地址,这段存放field索引信息的空间中则指向着所有的filed信息(此时还没有链接上,只是分配了空间)。这里的field有string,class,method,field,interface。

6.8 创建一个RawDexFile对象,并将缓存文件名,DvmDex等数据存放进去

7,将6中得到的RawDexFile等信息存放到DexOrJar指向的一个对象中,然后再将这个DexOrJar通过addToDexFileTable存放到hash表中。

至此,类加载器的创建就完成了。

阅读全文
0 0
原创粉丝点击