[Android]Dalvik的BOOTCLASSPATH和dexopt流程
BOOTCLASSPATH简介
1.BOOTCLASSPATH是Android Linux的一个环境变量,可以在adb shell下用$BOOTCLASSPATH看到。
2.BOOTCLASSPATH于/init.rc文件中export,如果没有找到的话,可以在init.rc中import的文件里找到(如import /init.environ.rc)。
3.init.rc文件存在于boot.img的ramdisk映像中。如果仅仅是修改/init.rc文件,重启后会被ramdisk恢复,所以直接修改是没有效果的。
4.boot.img是一种特殊的Android定制格式,由boot header,kernel,ramdisk以及second stage loader(可选)组成,详见android/system/core/mkbootimg/bootimg.h。
boot.img空间结构:
** +-----------------+
** | boot header | 1 page
** +-----------------+
** | kernel | n pages
** +-----------------+
** | ramdisk | m pages
** +-----------------+
** | second stage | o pages
** +-----------------+
典型的ramdisk文件结构:
./init.trout.rc
./default.prop
./proc
./dev
./init.rc
./init
./sys
./init.goldfish.rc
./sbin
./sbin/adbd
./system
./data
BOOTCLASSPATH的作用
以Android4.4手机的BOOTCLASSPATH为例:
export BOOTCLASSPATH /system/framework/core.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar...
当kernel启动时1号进程init解析init.rc,将/system/framework下的jar包路径export出来。
Dalvik虚拟机在初始化过程中,会读取环境变量BOOTCLASSPATH,用于之后的类加载和优化。
Dalvik虚拟机的启动和dexopt流程
从Dalvik虚拟机的启动过程分析一文可以知道,Zygote会在启动后创建Dalvik虚拟机实例,并进行初始化。
那我们就接着Dalvik虚拟机初始化后开始探究它是如何通过BOOTCLASSPATH来进行dex优化的:
1.1. VM initialization
android/dalvik/vm/Init.cpp
std::string dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv)
{
...
ALOGV("VM init args (%d):", argc);
... setCommandLineDefaults(); // ---> 读取BOOTCLASSPATH
...
if (!dvmClassStartup()) { // ---> 初始化bootstrap class loader
return "dvmClassStartup failed";
}
}
1.2. 读取BOOTCLASSPATH
static void setCommandLineDefaults()
{
const char* envStr = getenv("CLASSPATH");
if (envStr != NULL) {
gDvm.classPathStr = strdup(envStr);
} else {
gDvm.classPathStr = strdup(".");
}
envStr = getenv("BOOTCLASSPATH"); // 读取到BOOTCLASSPATH环境变量
if (envStr != NULL) {
gDvm.bootClassPathStr = strdup(envStr);
} else {
gDvm.bootClassPathStr = strdup(".");
}
...
}
就这样,BOOTCLASSPATH的值被保存到gDvm.bootClassPathStr中。
2.1. 初始化bootstrap class loader
android/dalvik/vm/oo/Class.cpp
bool dvmClassStartup()
{
... /*
* Process the bootstrap class path. This means opening the specified
* DEX or Jar files and possibly running them through the optimizer.
*/
assert(gDvm.bootClassPath == NULL);
processClassPath(gDvm.bootClassPathStr, true); // 下一步 if (gDvm.bootClassPath == NULL)
return false;
}
2.2. 将路径、Zip文件和Dex文件的list转换到ClassPathEntry结构体当中
static ClassPathEntry* processClassPath(const char* pathStr, bool isBootstrap)
{
ClassPathEntry* cpe = NULL;
...
if (!prepareCpe(&tmp, isBootstrap)) {}
}
2.3. 根据cpe打开文件
static bool prepareCpe(ClassPathEntry* cpe, bool isBootstrap)
{
... if ((strcmp(suffix, "jar") == 0) || (strcmp(suffix, "zip") == 0) ||
(strcmp(suffix, "apk") == 0)) {
JarFile* pJarFile = NULL;
/* 打开jar包,找到class.dex或jar包旁边的.odex文件 */
if (dvmJarFileOpen(cpe->fileName, NULL, &pJarFile, isBootstrap) == 0) {
cpe->kind = kCpeJar;
cpe->ptr = pJarFile;
return true;
}
} else if (strcmp(suffix, "dex") == 0) {
RawDexFile* pRawDexFile = NULL;
/* 与dvmJarFileOpen函数作用类似,是由它复制过来重构的 */
if (dvmRawDexFileOpen(cpe->fileName, NULL, &pRawDexFile, isBootstrap) == 0) {
cpe->kind = kCpeDex;
cpe->ptr = pRawDexFile;
return true;
}
} else {
ALOGE("Unknown type suffix '%s'", suffix);
}
...
}
3. 打开jar包,找到class.dex或jar包旁边的.odex文件
android/dalvik/vm/JarFile.cpp
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
... /* Even if we're not going to look at the archive, we need to
* open it so we can stuff it into ppJarFile.
*/
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
archiveOpen = true; /* If we fork/exec into dexopt, don't let it inherit the archive's fd.
*/
dvmSetCloseOnExec(dexZipGetArchiveFd(&archive)); /* First, look for a ".odex" alongside the jar file. It will
* have the same name/path except for the extension.
*/
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
if (fd >= 0) {
ALOGV("Using alternate file (odex) for %s ...", fileName);
/* 读、验证header和dependencies */
if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
ALOGE("%s odex has stale dependencies", fileName);
free(cachedName);
cachedName = NULL;
close(fd);
fd = -1;
goto tryArchive;
} else {
ALOGV("%s odex has good dependencies", fileName);
//TODO: make sure that the .odex actually corresponds
// to the classes.dex inside the archive (if present).
// For typical use there will be no classes.dex.
}
} else {
ZipEntry entry; tryArchive:
/*
* Pre-created .odex absent or stale. Look inside the jar for a
* "classes.dex".
*/
...
}
4.读、验证opt的header,读、验证dependencies
android/dalvik/vm/analysis/DexPrepare.cpp
bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen,
u4 crc, bool expectVerify, bool expectOpt)
{
...
/*
* Verify dependencies on other cached DEX files. It must match
* exactly with what is currently defined in the bootclasspath.
*/
ClassPathEntry* cpe;
u4 numDeps; numDeps = read4LE(&ptr);
ALOGV("+++ DexOpt: numDeps = %d", numDeps);
for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
const char* cacheFileName =
dvmPathToAbsolutePortion(getCacheFileName(cpe));
assert(cacheFileName != NULL); /* guaranteed by Class.c */ const u1* signature = getSignature(cpe);
size_t len = strlen(cacheFileName) +1;
u4 storedStrLen; if (numDeps == 0) {
/* more entries in bootclasspath than in deps list */
ALOGI("DexOpt: not all deps represented");
goto bail;
} storedStrLen = read4LE(&ptr);
if (len != storedStrLen ||
strcmp(cacheFileName, (const char*) ptr) != 0)
{
ALOGI("DexOpt: mismatch dep name: '%s' vs. '%s'",
cacheFileName, ptr);
goto bail;
} ptr += storedStrLen; if (memcmp(signature, ptr, kSHA1DigestLen) != 0) {
ALOGI("DexOpt: mismatch dep signature for '%s'", cacheFileName);
goto bail;
}
ptr += kSHA1DigestLen; ALOGV("DexOpt: dep match on '%s'", cacheFileName); numDeps--;
} if (numDeps != 0) {
/* more entries in deps list than in classpath */
ALOGI("DexOpt: Some deps went away");
goto bail;
}
...
}
实际应用
打通了Dalvik dexopt的这个流程,那这到底又有什么用呢?
让我们看看实际开发过程中的手机升级binary后无法boot到Home界面的log:
AndroidRuntime >>>>>> AndroidRuntime START com.android.internal.os.ZygoteInit <<<<<<
AndroidRuntime CheckJNI is ON
dalvikvm DexOpt: Some deps went away
dalvikvm /system/framework/core-junit.jar odex has stale dependencies
dalvikvm DexOpt: --- BEGIN 'core-junit.jar' (bootstrap=) ---
dalvikvm DexOpt: load 42ms, verify+opt 25ms, bytes
dalvikvm DexOpt: --- END 'core-junit.jar' (success) ---
dalvikvm DEX prep '/system/framework/core-junit.jar': unzip in 1ms, rewrite 126ms
dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'
dalvikvm /system/framework/bouncycastle.jar odex has stale dependencies
dalvikvm DexOpt: --- BEGIN 'bouncycastle.jar' (bootstrap=) ---
dalvikvm DexOpt: Some deps went away
dalvikvm /system/framework/core-junit.jar odex has stale dependencies
dalvikvm DexOpt: load 33ms, verify+opt 350ms, bytes
dalvikvm DexOpt: --- END 'bouncycastle.jar' (success) ---
dalvikvm DEX prep '/system/framework/bouncycastle.jar': unzip in 57ms, rewrite 548ms
dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'
dalvikvm /system/framework/ext.jar odex has stale dependencies
dalvikvm DexOpt: --- BEGIN 'ext.jar' (bootstrap=) ---
dalvikvm DexOpt: Some deps went away
dalvikvm /system/framework/core-junit.jar odex has stale dependencies
dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'
dalvikvm /system/framework/bouncycastle.jar odex has stale dependencies
根据前面的流程,结合log我们就可以分析出,DexOpt: mismatch dep name: '/data/dalvik-cache/system@framework@core-junit.jar@classes.dex' vs. '/system/framework/conscrypt.odex'是错误所在,是由于data/dalvik-cache/下的dex cache文件和system/framework/下的jar文件验证依赖关系时候对应不上。
从函数dvmCheckOptHeaderAndDependencies()可以得知,BOOTCLASSPATH和cache必须是完全一致的
尝试删除所有cache文件,重启还是不行。那么应该想到BOOTCLASSPATH和实际的system/framework/的jar包不一致,才会导致和其生成的cache不一致。
对比一下果然不一致,issue trouble-shooted.
解决方法:把对应boot.img也烧进去,这样BOOTCLASSPATH就能更新一致,dex优化就能正确进行下去。
[Android]Dalvik的BOOTCLASSPATH和dexopt流程的更多相关文章
- Android逆向基础----Android Dalvik虚拟机
Android Dalvik虚拟机的特点: l 体积小,占用内存空间小. l 专有DEX可执行文件. l 常量池采用32位索引值,寻址类方法名,字段名,常量更快. l 基于寄存器架构,并拥有一 ...
- Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. ...
- Android 7.0 Power 按键处理流程
Android 7.0 Power 按键处理流程 Power按键的处理逻辑由PhoneWindowManager来完成,本文只关注PhoneWindowManager中与Power键相关的内容,其他 ...
- android搜索框列表布局,流程及主要步骤思维导图
android搜索框列表布局,流程及主要步骤思维导图 android搜索框列表布局,流程及主要步骤思维导图 activity_coin_search.xml----------<com.scwa ...
- Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象.与Google原生AOSP有些许差异.请读者知悉. ...
- Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程
本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. ...
- unity3d-配置Android环境,打包发布Apk流程详解
31:unity3d-配置Android环境,打包发布Apk流程详解 作者 阿西纳尼 关注 2016.08.28 22:52 字数 498 阅读 1806评论 0喜欢 5 Unity配置Android ...
- Android Framework层Power键关机流程(一,Power长按键操作处理)
一:Android处理Power按键长按操作 在Framework层中,Android4.x对Power键(KeyEvent.KEYCODE_POWER)的操作,我们从PhoneWindowManag ...
- Dalvik虚拟机java方法执行流程和Method结构体分析
Method结构体是啥? 在Dalvik虚拟机内部,每个Java方法都有一个对应的Method结构体,虚拟机根据此结构体获取方法的所有信息. Method结构体是怎样定义的? 此结构体在不同的andr ...
随机推荐
- 黑马程序员_<<StringBuffer,包装类>>
--------------------ASP.Net+Android+IOS开发..Net培训.期待与您交流! -------------------- 1. StringBuffer 1.概述 S ...
- HttpUrlConnection get和post简单实现(疑惑解决)
近期研究微信的公众平台开发.须要和微信的server进行数据读取,简单研究了下jdk自带的HttpUrlConnection类(URLConnection的子类),简单实现了一下微信的access_t ...
- POJ 1556 The Doors(计算几何+最短路)
这题就是,处理出没两个点.假设能够到达,就连一条边,推断可不能够到达,利用线段相交去推断就可以.最后求个最短路就可以 代码: #include <cstdio> #include < ...
- 与html相关的知识点整理
梳理html时发现的一些问题.总结一下,答案大都从网上找来. 一.html 与 htm 的区别 没有本质的区别..htm是在win32时代,系统只能识别3位扩展名时使用的.现在一般都使用.html. ...
- POJ 2115 模线性方程 ax=b(mod n)
/* (x*c+a)%(2^k)==b →(x*c)%(2^k)==b-a 满足定理: 推论1:方程ax=b(mod n)对于未知量x有解,当且仅当gcd(a,n) | b. 推论2:方程ax=b(m ...
- 安装cocopods 更新ruby
最近弄cocoapods出现如标题提示 So度娘一番.感谢作者.纯属学习没有商业用途 1.打开终端 终端输入 $ruby -v 查看ruby的版本 打印代码: ruby 2.0.0p648 (2 ...
- C++读写文件的简单例子
#include <iostream> #include <fstream> using namespace std; void main() { ofstream in; i ...
- jbpmAPI-7
7.1. Introduction 业务流程的一个重要方面是人工任务管理.虽然一些执行的工作过程中可以自动执行一些任务需要执行的人类演员.jBPM支持特殊的人工任务节点内部流程建模与人类用户交互.这个 ...
- Bootstrap第一天
1.代码引入: 第一步:在html5文档 <meta name="viewport" content="width=device-width, initial-sc ...
- Java 日期字符串与日期类型转换
1.SimpleDateFormat.format 把日期类型转化到指定格式字符串 public static String convToString(Calendar cld,String temp ...