最近在看其他的源代码,Android的源码几天没看了。
假期的计划总赶不上变化,尴尬。。。
接着Android应用的安装,看看优化(odex)的过程源码
上篇讲到在installd.c当中,命令参数与cmds中保存的命令进行匹配。
cmds中保存的命令如下
struct cmdinfo {
const char *name;
unsigned numargs;
int (*func)(char **arg, char reply[REPLY_MAX]);
};
struct cmdinfo cmds[] = {
{ "ping", 0, do_ping },
{ "install", 4, do_install },
{ "dexopt", 3, do_dexopt },
{ "movedex", 2, do_move_dex },
{ "rmdex", 1, do_rm_dex },
{ "remove", 2, do_remove },
{ "rename", 2, do_rename },
{ "fixuid", 3, do_fixuid },
{ "freecache", 1, do_free_cache },
{ "rmcache", 2, do_rm_cache },
{ "getsize", 6, do_get_size },
{ "rmuserdata", 2, do_rm_user_data },
{ "movefiles", 0, do_movefiles },
{ "linklib", 3, do_linklib },
{ "mkuserdata", 3, do_mk_user_data },
{ "rmuser", 1, do_rm_user },
};
其中,dexopt命令就是应用优化命令
do_dexopt函数
static int do_dexopt(char **arg, char reply[REPLY_MAX])
{
/* apk_path, uid, is_public */
return dexopt(arg[0], atoi(arg[1]), atoi(arg[2]));
}
dexopt函数位于同目录下的commands.c文件中
int dexopt(const char *apk_path, uid_t uid, int is_public)
{
struct utimbuf ut;
struct stat apk_stat, dex_stat;
char dex_path[PKG_PATH_MAX];
char dexopt_flags[PROPERTY_VALUE_MAX];
char *end;
int res, zip_fd=-1, odex_fd=-1;
/* Before anything else: is there a .odex file? If so, we have
* pre-optimized the apk and there is nothing to do here.
*/
if (strlen(apk_path) >= (PKG_PATH_MAX - 8)) {
return -1;
}
/* platform-specific flags affecting optimization and verification */
property_get("dalvik.vm.dexopt-flags", dexopt_flags, "");
strcpy(dex_path, apk_path);
end = strrchr(dex_path, '.');
if (end != NULL) {
strcpy(end, ".odex");
if (stat(dex_path, &dex_stat) == 0) {
return 0;
}
}
if (create_cache_path(dex_path, apk_path)) {
return -1;
}
memset(&apk_stat, 0, sizeof(apk_stat));
stat(apk_path, &apk_stat);
zip_fd = open(apk_path, O_RDONLY, 0);
if (zip_fd < 0) {
ALOGE("dexopt cannot open '%s' for input\n", apk_path);
return -1;
}
unlink(dex_path);
odex_fd = open(dex_path, O_RDWR | O_CREAT | O_EXCL, 0644);
if (odex_fd < 0) {
ALOGE("dexopt cannot open '%s' for output\n", dex_path);
goto fail;
}
if (fchmod(odex_fd,
S_IRUSR|S_IWUSR|S_IRGRP |
(is_public ? S_IROTH : 0)) < 0) {
ALOGE("dexopt cannot chmod '%s'\n", dex_path);
goto fail;
}
if (fchown(odex_fd, AID_SYSTEM, uid) < 0) {
ALOGE("dexopt cannot chown '%s'\n", dex_path);
goto fail;
}
ALOGV("DexInv: --- BEGIN '%s' ---\n", apk_path);
pid_t pid;
pid = fork();
if (pid == 0) {
/* child -- drop privileges before continuing */
if (setgid(uid) != 0) {
ALOGE("setgid(%d) failed during dexopt\n", uid);
exit(64);
}
if (setuid(uid) != 0) {
ALOGE("setuid(%d) during dexopt\n", uid);
exit(65);
}
// drop capabilities
struct __user_cap_header_struct capheader;
struct __user_cap_data_struct capdata[2];
memset(&capheader, 0, sizeof(capheader));
memset(&capdata, 0, sizeof(capdata));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
if (capset(&capheader, &capdata[0]) < 0) {
ALOGE("capset failed: %s\n", strerror(errno));
exit(66);
}
if (flock(odex_fd, LOCK_EX | LOCK_NB) != 0) {
ALOGE("flock(%s) failed: %s\n", dex_path, strerror(errno));
exit(67);
}
run_dexopt(zip_fd, odex_fd, apk_path, dexopt_flags);
exit(68); /* only get here on exec failure */
} else {
res = wait_dexopt(pid, apk_path);
if (res != 0) {
ALOGE("dexopt failed on '%s' res = %d\n", dex_path, res);
goto fail;
}
}
ut.actime = apk_stat.st_atime;
ut.modtime = apk_stat.st_mtime;
utime(dex_path, &ut);
close(odex_fd);
close(zip_fd);
return 0;
fail:
if (odex_fd >= 0) {
close(odex_fd);
unlink(dex_path);
}
if (zip_fd >= 0) {
close(zip_fd);
}
return -1;
}
构造路径,打开原文件,打开(没有就创建)odex文件,然后fork一个进程执行优化
主要的优化在run_dexopt函数中
static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
const char* dexopt_flags)
{
static const char* DEX_OPT_BIN = "/system/bin/dexopt";
static const int MAX_INT_LEN = 12; // '-'+10dig+'\0' -OR- 0x+8dig
char zip_num[MAX_INT_LEN];
char odex_num[MAX_INT_LEN];
sprintf(zip_num, "%d", zip_fd);
sprintf(odex_num, "%d", odex_fd);
execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
dexopt_flags, (char*) NULL);
ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
}
用execl执行/system/bin/dexopt
/*
* Main entry point. Decide where to go.
*/
int main(int argc, char* const argv[])
{
set_process_name("dexopt");
setvbuf(stdout, NULL, _IONBF, 0);
if (argc > 1) {
if (strcmp(argv[1], "--zip") == 0)
return fromZip(argc, argv);
else if (strcmp(argv[1], "--dex") == 0)
return fromDex(argc, argv);
else if (strcmp(argv[1], "--preopt") == 0)
return preopt(argc, argv);
}
fprintf(stderr,
"Usage:\n\n"
"Short version: Don't use this.\n\n"
"Slightly longer version: This system-internal tool is used to\n"
"produce optimized dex files. See the source code for details.\n");
return 1;
}
根据"--zip",执行fromZip
static int fromZip(int argc, char* const argv[])
{
int result = -1;
int zipFd, cacheFd;
const char* zipName;
char* bcpCopy = NULL;
const char* dexoptFlags;
if (argc != 6) {
ALOGE("Wrong number of args for --zip (found %d)", argc);
goto bail;
}
/* skip "--zip" */
argc--;
argv++;
GET_ARG(zipFd, strtol, "bad zip fd");
GET_ARG(cacheFd, strtol, "bad cache fd");
zipName = *++argv;
--argc;
dexoptFlags = *++argv;
--argc;
result = processZipFile(zipFd, cacheFd, zipName, dexoptFlags);
bail:
return result;
}
GET_ARG是个同文件下的宏定义
/* advance to the next arg and extract it */
#define GET_ARG(_var, _func, _msg) \
{ \
char* endp; \
(_var) = _func(*++argv, &endp, 0); \
if (*endp != '\0') { \
ALOGE("%s '%s'", _msg, *argv); \
goto bail; \
} \
--argc; \
}
函数的最后,也是核心,调用processZipFile
/*
* Common functionality for normal device-side processing as well as
* preoptimization.
*/
static int processZipFile(int zipFd, int cacheFd, const char* zipName,
const char *dexoptFlags)
{
char* bcpCopy = NULL;
/*
* Check to see if this is a bootstrap class entry. If so, truncate
* the path.
*/
const char* bcp = getenv("BOOTCLASSPATH");
if (bcp == NULL) {
ALOGE("DexOptZ: BOOTCLASSPATH not set");
return -1;
}
bool isBootstrap = false;
const char* match = strstr(bcp, zipName);
if (match != NULL) {
/*
* TODO: we have a partial string match, but that doesn't mean
* we've matched an entire path component. We should make sure
* that we're matching on the full zipName, and if not we
* should re-do the strstr starting at (match+1).
*
* The scenario would be a bootclasspath with something like
* "/system/framework/core.jar" while we're trying to optimize
* "/framework/core.jar". Not very likely since all paths are
* absolute and end with ".jar", but not impossible.
*/
int matchOffset = match - bcp;
if (matchOffset > 0 && bcp[matchOffset-1] == ':')
matchOffset--;
ALOGV("DexOptZ: found '%s' in bootclasspath, cutting off at %d",
zipName, matchOffset);
bcpCopy = strdup(bcp);
bcpCopy[matchOffset] = '\0';
bcp = bcpCopy;
ALOGD("DexOptZ: truncated BOOTCLASSPATH to '%s'", bcp);
isBootstrap = true;
}
int result = extractAndProcessZip(zipFd, cacheFd, zipName, isBootstrap,
bcp, dexoptFlags);
free(bcpCopy);
return result;
}
processZipFile进行包装验证后调用extractAndProcessZip
/*
* Extract "classes.dex" from zipFd into "cacheFd", leaving a little space
* up front for the DEX optimization header.
*/
static int extractAndProcessZip(int zipFd, int cacheFd,
const char* debugFileName, bool isBootstrap, const char* bootClassPath,
const char* dexoptFlagStr)
{
ZipArchive zippy;
ZipEntry zipEntry;
size_t uncompLen;
long modWhen, crc32;
off_t dexOffset;
int err;
int result = -1;
int dexoptFlags = 0; /* bit flags, from enum DexoptFlags */
DexClassVerifyMode verifyMode = VERIFY_MODE_ALL;
DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED;
memset(&zippy, 0, sizeof(zippy));
/* make sure we're still at the start of an empty file */
if (lseek(cacheFd, 0, SEEK_END) != 0) {
ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName);
goto bail;
}
/*
* Write a skeletal DEX optimization header. We want the classes.dex
* to come just after it.
*/
err = dexOptCreateEmptyHeader(cacheFd);
if (err != 0)
goto bail;
/* record the file position so we can get back here later */
dexOffset = lseek(cacheFd, 0, SEEK_CUR);
if (dexOffset < 0)
goto bail;
/*
* Open the zip archive, find the DEX entry.
*/
if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) {
ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);
goto bail;
}
zipEntry = dexZipFindEntry(&zippy, kClassesDex);
if (zipEntry == NULL) {
ALOGW("DexOptZ: zip archive '%s' does not include %s",
debugFileName, kClassesDex);
goto bail;
}
/*
* Extract some info about the zip entry.
*/
if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,
&modWhen, &crc32) != 0)
{
ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);
goto bail;
}
uncompLen = uncompLen;
modWhen = modWhen;
crc32 = crc32;
/*
* Extract the DEX data into the cache file at the current offset.
*/
if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) {
ALOGW("DexOptZ: extraction of %s from %s failed",
kClassesDex, debugFileName);
goto bail;
}
/* Parse the options. */
if (dexoptFlagStr[0] != '\0') {
const char* opc;
const char* val;
opc = strstr(dexoptFlagStr, "v="); /* verification */
if (opc != NULL) {
switch (*(opc+2)) {
case 'n': verifyMode = VERIFY_MODE_NONE; break;
case 'r': verifyMode = VERIFY_MODE_REMOTE; break;
case 'a': verifyMode = VERIFY_MODE_ALL; break;
default: break;
}
}
opc = strstr(dexoptFlagStr, "o="); /* optimization */
if (opc != NULL) {
switch (*(opc+2)) {
case 'n': dexOptMode = OPTIMIZE_MODE_NONE; break;
case 'v': dexOptMode = OPTIMIZE_MODE_VERIFIED; break;
case 'a': dexOptMode = OPTIMIZE_MODE_ALL; break;
case 'f': dexOptMode = OPTIMIZE_MODE_FULL; break;
default: break;
}
}
opc = strstr(dexoptFlagStr, "m=y"); /* register map */
if (opc != NULL) {
dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;
}
opc = strstr(dexoptFlagStr, "u="); /* uniprocessor target */
if (opc != NULL) {
switch (*(opc+2)) {
case 'y': dexoptFlags |= DEXOPT_UNIPROCESSOR; break;
case 'n': dexoptFlags |= DEXOPT_SMP; break;
default: break;
}
}
}
/*
* Prep the VM and perform the optimization.
*/
if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,
dexoptFlags) != 0)
{
ALOGE("DexOptZ: VM init failed");
goto bail;
}
//vmStarted = 1;
/* do the optimization */
if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,
modWhen, crc32, isBootstrap))
{
ALOGE("Optimization failed");
goto bail;
}
/* we don't shut the VM down -- process is about to exit */
result = 0;
bail:
dexZipCloseArchive(&zippy);
return result;
}
这个函数可以很明显的看出来分为几段,首先构造一个空文件和空文件头,将原来文件中的部分数据拷贝到空文件中。
其次,解析设置参数(Parse the options)
最后准备和执行优化,dvmPrepForDexOpt和dvmContinueOptimization函数
重点看后一个dvmContinueOptimization函数
该函数位于dalvik\vm\analysis\DexPrepare.cpp中
函数很长,分段来看
bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
DexClassLookup* pClassLookup = NULL;
RegisterMapBuilder* pRegMapBuilder = NULL;
assert(gDvm.optimizing);
ALOGV("Continuing optimization (%s, isb=%d)", fileName, isBootstrap);
assert(dexOffset >= 0);
/* quick test so we don't blow up on empty file */
if (dexLength < (int) sizeof(DexHeader)) {
ALOGE("too small to be DEX");
return false;
}
if (dexOffset < (int) sizeof(DexOptHeader)) {
ALOGE("not enough room for opt header");
return false;
}
bool result = false;
声明变量,检查长度
/*
* Drop this into a global so we don't have to pass it around. We could
* also add a field to DexFile, but since it only pertains to DEX
* creation that probably doesn't make sense.
*/
gDvm.optimizingBootstrapClass = isBootstrap;
{
/*
* Map the entire file (so we don't have to worry about page
* alignment). The expectation is that the output file contains
* our DEX data plus room for a small header.
*/
bool success;
void* mapAddr;
mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapAddr == MAP_FAILED) {
ALOGE("unable to mmap DEX cache: %s", strerror(errno));
goto bail;
}
bool doVerify, doOpt;
if (gDvm.classVerifyMode == VERIFY_MODE_NONE) {
doVerify = false;
} else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) {
doVerify = !gDvm.optimizingBootstrapClass;
} else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ {
doVerify = true;
}
if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) {
doOpt = false;
} else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||
gDvm.dexOptMode == OPTIMIZE_MODE_FULL) {
doOpt = doVerify;
} else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ {
doOpt = true;
}
将文件内容mmap到mapAddr,根据从全局变量gDvm传递的信息设置选项
/*
* Rewrite the file. Byte reordering, structure realigning,
* class verification, and bytecode optimization are all performed
* here.
*
* In theory the file could change size and bits could shift around.
* In practice this would be annoying to deal with, so the file
* layout is designed so that it can always be rewritten in place.
*
* This creates the class lookup table as part of doing the processing.
*/
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
doVerify, doOpt, &pClassLookup, NULL);
if (success) {
DvmDex* pDvmDex = NULL;
u1* dexAddr = ((u1*) mapAddr) + dexOffset;
if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
ALOGE("Unable to create DexFile");
success = false;
} else {
/*
* If configured to do so, generate register map output
* for all verified classes. The register maps were
* generated during verification, and will now be serialized.
*/
if (gDvm.generateRegisterMaps) {
pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);
if (pRegMapBuilder == NULL) {
ALOGE("Failed generating register maps");
success = false;
}
}
DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;
updateChecksum(dexAddr, dexLength, pHeader);
dvmDexFileFree(pDvmDex);
}
}
从原文注释就可以看到,这段很关键,文件重写,比特位、结构重排,类核实,字节码优化都在这部分
/* unmap the read-write version, forcing writes to disk */
if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0) {
ALOGW("msync failed: %s", strerror(errno));
// weird, but keep going
}
#if 1
/*
* This causes clean shutdown to fail, because we have loaded classes
* that point into it. For the optimizer this isn't a problem,
* because it's more efficient for the process to simply exit.
* Exclude this code when doing clean shutdown for valgrind.
*/
if (munmap(mapAddr, dexOffset + dexLength) != 0) {
ALOGE("munmap failed: %s", strerror(errno));
goto bail;
}
#endif
if (!success)
goto bail;
}
这段没什么好说的
/* get start offset, and adjust deps start for 64-bit alignment */
off_t depsOffset, optOffset, endOffset, adjOffset;
int depsLength, optLength;
u4 optChecksum;
depsOffset = lseek(fd, 0, SEEK_END);
if (depsOffset < 0) {
ALOGE("lseek to EOF failed: %s", strerror(errno));
goto bail;
}
adjOffset = (depsOffset + 7) & ~(0x07);
if (adjOffset != depsOffset) {
ALOGV("Adjusting deps start from %d to %d",
(int) depsOffset, (int) adjOffset);
depsOffset = adjOffset;
lseek(fd, depsOffset, SEEK_SET);
}
/*
* Append the dependency list.
*/
if (writeDependencies(fd, modWhen, crc) != 0) {
ALOGW("Failed writing dependencies");
goto bail;
}
/* compute deps length, then adjust opt start for 64-bit alignment */
optOffset = lseek(fd, 0, SEEK_END);
depsLength = optOffset - depsOffset;
adjOffset = (optOffset + 7) & ~(0x07);
if (adjOffset != optOffset) {
ALOGV("Adjusting opt start from %d to %d",
(int) optOffset, (int) adjOffset);
optOffset = adjOffset;
lseek(fd, optOffset, SEEK_SET);
}
/*
* Append any optimized pre-computed data structures.
*/
if (!writeOptData(fd, pClassLookup, pRegMapBuilder)) {
ALOGW("Failed writing opt data");
goto bail;
}
endOffset = lseek(fd, 0, SEEK_END);
optLength = endOffset - optOffset;
/* compute checksum from start of deps to end of opt area */
if (!computeFileChecksum(fd, depsOffset,
(optOffset+optLength) - depsOffset, &optChecksum))
{
goto bail;
}
/*
* Output the "opt" header with all values filled in and a correct
* magic number.
*/
DexOptHeader optHdr;
memset(&optHdr, 0xff, sizeof(optHdr));
memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);
memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);
optHdr.dexOffset = (u4) dexOffset;
optHdr.dexLength = (u4) dexLength;
optHdr.depsOffset = (u4) depsOffset;
optHdr.depsLength = (u4) depsLength;
optHdr.optOffset = (u4) optOffset;
optHdr.optLength = (u4) optLength;
#if __BYTE_ORDER != __LITTLE_ENDIAN
optHdr.flags = DEX_OPT_FLAG_BIG;
#else
optHdr.flags = 0;
#endif
optHdr.checksum = optChecksum;
fsync(fd); /* ensure previous writes go before header is written */
lseek(fd, 0, SEEK_SET);
if (sysWriteFully(fd, &optHdr, sizeof(optHdr), "DexOpt opt header") != 0)
goto bail;
ALOGV("Successfully wrote DEX header");
result = true;
//dvmRegisterMapDumpStats();
bail:
dvmFreeRegisterMapBuilder(pRegMapBuilder);
free(pClassLookup);
return result;
}
这段有几个关键,writeDependencies添加依赖列表,writeOptData写入预计算优化信息(类索引、寄存器映射)
然后对表头进行修改
整个优化后的odex文件详见我老大的博客http://blog.csdn.net/roland_sun/article/details/47183119
最后借我老大的图一用