的好文章,一定要转。
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
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
static
void
setCommandLineDefaults()
{
const
char
* envStr =
getenv
(
"CLASSPATH"
);
if
(envStr != NULL) {
gDvm.classPathStr = strdup(envStr);
}
else
{
gDvm.classPathStr = strdup(
"."
);
}
envStr =
getenv
(
"BOOTCLASSPATH"
);
// 读取到BOOTCLASSPATH环境变量<br>
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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结构体当中
1
2
3
4
5
6
|
static
ClassPathEntry* processClassPath(
const
char
* pathStr,
bool
isBootstrap)
{
ClassPathEntry* cpe = NULL;
...
if
(!prepareCpe(&tmp, isBootstrap)) {}
}
|
2.3. 根据cpe打开文件
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
|
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
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
|
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
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
|
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:
1 AndroidRuntime >>>>>> AndroidRuntime START com.android.internal.os.ZygoteInit <<<<<< 2 AndroidRuntime CheckJNI is ON 3 dalvikvm DexOpt: Some deps went away 4 dalvikvm /system/framework/core-junit.jar odex has stale dependencies 5 dalvikvm DexOpt: --- BEGIN 'core-junit.jar' (bootstrap=1) --- 6 dalvikvm DexOpt: load 42ms, verify+opt 25ms, 143956 bytes 7 dalvikvm DexOpt: --- END 'core-junit.jar' (success) --- 8 dalvikvm DEX prep '/system/framework/core-junit.jar': unzip in 1ms, rewrite 126ms 9 dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@[email protected]@classes.dex' vs. '/system/framework/conscrypt.odex' 10 dalvikvm /system/framework/bouncycastle.jar odex has stale dependencies 11 dalvikvm DexOpt: --- BEGIN 'bouncycastle.jar' (bootstrap=1) --- 12 dalvikvm DexOpt: Some deps went away 13 dalvikvm /system/framework/core-junit.jar odex has stale dependencies 14 dalvikvm DexOpt: load 33ms, verify+opt 350ms, 681812 bytes 15 dalvikvm DexOpt: --- END 'bouncycastle.jar' (success) --- 16 dalvikvm DEX prep '/system/framework/bouncycastle.jar': unzip in 57ms, rewrite 548ms 17 dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@[email protected]@classes.dex' vs. '/system/framework/conscrypt.odex' 18 dalvikvm /system/framework/ext.jar odex has stale dependencies 19 dalvikvm DexOpt: --- BEGIN 'ext.jar' (bootstrap=1) --- 20 dalvikvm DexOpt: Some deps went away 21 dalvikvm /system/framework/core-junit.jar odex has stale dependencies 22 dalvikvm DexOpt: mismatch dep name: '/data/dalvik-cache/system@[email protected]@classes.dex' vs. '/system/framework/conscrypt.odex' 23 dalvikvm /system/framework/bouncycastle.jar odex has stale dependencies
根据前面的流程,结合log我们就可以分析出,DexOpt: mismatch dep name: '/data/dalvik-cache/system@[email protected]@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优化就能正确进行下去。