DexClassLoader动态加载分析
转载自: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<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
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
DexClassLoader动态加载分析的更多相关文章
- Ext create动态加载分析
主要涉及到Ext.js Inventory.js ClassManager.js Class.js Loader.js Boot.js 在ClasManager.js的Ext.create中 Ext. ...
- 动态加载框架DL分析
动态加载框架DL分析 插件化开发,主要解决三个问题1.动态加载未安装的apk,dex,jar等文件2.activity生命周期的问题,还有service3.Android的资源调用的问题 简单说一下怎 ...
- ExtJs 通过分析源代码解决动态加载Controller的问题
通过分析源代码解决动态加载Controller的问题 最近在研究ExtJs(4.2.0)的MVC开发模式,具体Extjs的MVC如何使用这里不解释,具体参见ExtJs的官方文档.这里要解决的问题是如何 ...
- Android动态加载学习笔记(一)
前言 上周五DPAndroid小分队就第二阶段分享内容进行了讨论,结果形成了三个主题:性能优化.动态加载.内核远离.我选择的是第二项——动态加载.在目前的Android开发中,这一部分知识还是比较流行 ...
- [转载] Android动态加载Dex机制解析
本文转载自: http://blog.csdn.net/wy353208214/article/details/50859422 1.什么是类加载器? 类加载器(class loader)是 Java ...
- Android中插件开发篇之----动态加载Activity(免安装运行程序)
一.前言 又到周末了,时间过的很快,今天我们来看一下Android中插件开发篇的最后一篇文章的内容:动态加载Activity(免安装运行程序),在上一篇文章中说道了,如何动态加载资源(应用换肤原理解析 ...
- Android 动态加载 (二) 态加载机制 案例二
探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法 重要说明 在实践的过程中大家都会发现资源引用的问题,这里重点声明两点: 1. 资源文件是不能直接inflate的,如果简单的话直接在程序 ...
- Android 动态加载(防止逆向编译) jar混淆加密
最近工作中接到了一个研究防止逆向编译的任务.研究了几天资料,最后基本实现了防破解技术,在这个工程中,也略有一些心得体会,现整理下来分享,供大家探讨参考研究.文中如有纰漏.失实之处,请大家及时给与指正. ...
- Adroid动态加载Apk-插件化技术框架(动态代理方案)
技术:Android + java +动态加载+插件化 概述 为什么要使用插件化?在开发中,一个项目只会越做越大.初始版本可能是单一功能,后续可能加上各种风马牛不相及的功能.所以我认为插件化可以使 ...
随机推荐
- C语言II博客作业01
这个作业属于那个课程 https://edu.cnblogs.com/campus/zswxy/SE2020-4 这个作业要求在哪里 https://edu.cnblogs.com/campus/zs ...
- Scientific Internet Access
下载小飞机 https://github.com/shadowsocksr-backup 寻找ssr https://github.com/Alvin9999/new-pac/wiki/ss%E5%8 ...
- 攻防世界 reverse evil
这是2017 ddctf的一道逆向题, 挑战:<恶意软件分析> 赛题背景: 员工小A收到了一封邮件,带一个文档附件,小A随手打开了附件.随后IT部门发现小A的电脑发出了异常网络访问请求,进 ...
- Redis入门到放弃系列-redis数据类型
Redis数据类型? Redis 提供一些常用的数据类型:Strings.Lists.Sets.Sorted sets.Hashes.Arrays.Bitmap.Streams Strings(字符串 ...
- Swagger接口如何生成Html离线文档
A very simple tool that converts Swagger Api Document to Html File. 小记Swagger接口生成Html离线文档 由来 很多人用swa ...
- JS中dom操作的事件
Click--点击事件 优先级:dom.onclick 高于标签上的onClick属性 监听事件 --不会覆盖前面的事件效果 dom.addEventListener() 括号里面有三个参数 1 ...
- IndentationError:unexpected indent”、“IndentationError:unindent does not match any outer indetation level”以及“IndentationError:expected an indented block Python常见错误
错误的使用缩进量 记住缩进增加只用在以:结束的语句之后,而之后必须恢复到之前的缩进格式. 经典错误,一定要注意缩进,尤其是在非界面化下环境的代码修改
- 云计算和AI时代,运维应该如何做好转型?
云计算和AI时代,运维应该如何做好转型? 今天我们来聊一聊,在云计算和AI时代,运维应该如何做好转型?今天的内容可以说是我们前面运维组织架构和协作模式转型的姊妹篇.针对运维转型这个话题,谈谈我的思考和 ...
- 随便聊聊 Java 8 的函数式编程
函数式编程(Functional Programming) 首先,我们来了解一个叫做"编程范式"的概念. 什么是"编程范式"呢?简单来说就是指导我们编程的方法论 ...
- 201871030110-何飞 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告
201871030110-何飞 实验三 结对项目-<D{0-1}KP 实例数据集算法实验平台>项目报告 项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 ...