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

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值