Dalvik源碼閱讀筆記(一)
dalvik 虛擬機啟動入口在 JNI_CreateJavaVM(), 在進行完 JNIEnv 等環境設置後,調用 dvmStartup() 函數進行真正的 DVM 初始化。
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
--snip--
std::string status = dvmStartup(argc, argv.get(),
args->ignoreUnrecognized, (JNIEnv*)pEnv);
--snip--
}
dvmStartup() 進行了一系列 DVM 子模塊初始化工作,主要關註 dvmInternalNativeStartup() ,這個函數用來初始化一個內部 Native 函數集。
std::string dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv)
{
--snip--
if (!dvmInlineNativeStartup()) {
return "dvmInlineNativeStartup";
}
--snip--
}
所有需要直接訪問 Dalvik 虛擬機內部函數或者數據結構的 Native 函數都需要定義在這個集合中,dvmInlineNativeStartup() 創建了函數集的 HashTable 用於快速訪問。
bool dvmInternalNativeStartup()
{
DalvikNativeClass* classPtr = gDvmNativeMethodSet;
--snip--
gDvm.userDexFiles = dvmHashTableCreate(2, dvmFreeDexOrJar);
--snip--
}
查看 gDvmNativeMethodSet 變量定義:
static DalvikNativeClass gDvmNativeMethodSet[] = { --snip-- { "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 }, --snip-- };
所有與 DEX 文件操作相關的 Native 函數都定義在 dvm_dalvik_system_DexFile() 中
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFileNative },
{ "openDexFile", "([B)I",
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ "closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile },
{ "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClassNative },
{ "getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList },
{ "isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};
openDexFileNative 與 openDexFile 函用於打開一個 DEX 文件,區別是 openDexFile 是 Opening in-memory DEX,而 openDexFileNative 讀取的是磁盤文件。
Dalvik_dalvik_system_DexFile_openDexFileNative() 函數用於打開 jar 或 DEX 文件。
這裏的 jar 也可以是 APK 文件,即用戶安裝的應用,他們本質都是 zip 壓縮包。
DEX 文件走 dvmRawDexFileOpen() 函數分支。
jar 文件走 dvmJarFileOpen() 函數分支,這是最常見的方式。
--snip--
} 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 {
--snip--
dvmJarFileOpen() 執行成功後,在 pJarFile 中保存了 zip 文件,dex(實際為 opt 過的 odex 文件) 文件加載到內存的地址:
struct JarFile {
ZipArchive archive;
//MemMapping map;
char* cacheFileName;
DvmDex* pDvmDex;
};
繼續看 dvmJarFileOpen() 函數實現:
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
/* 將 zip 讀入內存 archive 中 */
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
if (fd >= 0) {
/* 如果緩存中存在 fileName.odex 文件 */
if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) { /* 檢查如果不是新的,跳轉到 tryArchive */
ALOGE("%s odex has stale dependencies", fileName);
goto tryArchive;
} else {
ALOGV("%s odex has good dependencies", fileName);
}
} else {
ZipEntry entry;
tryArchive:
/* 查找 zip 中的 DEX 文件 */
entry = dexZipFindEntry(&archive, kDexInJarName);
if (entry != NULL) {
/* 優化為 odex */
dvmOptimizeDexFile(...);
}
}
/* 將 odex 文件映射到內存 pDvmDex */
if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
goto bail;
}
/* 初始化 pJarFile 結構體 */
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName = cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
}
DEX 在內存加載完成後,掛載到 HashTable 上:
addToDexFileTable(pDexOrJar);
dvmOptimizeDexFile() 是一個重要的函數,它通過 fork 執行 /bin/dexopt 程序進行 DEX 優化操作。代碼在 OptMain.cpp 中
dvmOptimizeDexFile() 設置了“–dex”參數,它決定了 dexopt 函數中的分支:
argv[curArg++] = "--dex";
int main(int argc, char* const argv[])
{
set_process_name("dexopt");
--snip--
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);
}
--snip--
}
–dex 走 fromDex() 分支,主要優化在 dvmContinueOptimization() 函數完成:
/* do the optimization */
if (!dvmContinueOptimization(fd, offset, length, debugFileName,
modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
{
ALOGE("Optimization failed");
goto bail;
}
bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
--snip--
/* 映射整個文件到內存 */
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;
}
/* rewriteDex 主要做對齊,大小端轉換等操作 */
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength, doVerify, doOpt, &pClassLookup, NULL);
u1* dexAddr = ((u1*) mapAddr) + dexOffset;
/* 經常在這個裏下斷或Hook脫殼,因為完成了rewriteDex
完成由 DEX File 到內存 pDvmDex 結構的映射
*/
if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
ALOGE("Unable to create DexFile");
success = false;
}
/* 最後生成 odex 文件 */
--snip--
}
int dvmDexFileOpenPartial (const void* addr, int len, DvmDex** ppDvmDex)
{
DvmDex* pDvmDex;
DexFile* pDexFile;
--snip--
pDexFile = dexFileParse((u1*)addr, len, parseFlags);
pDvmDex = allocateAuxStructures(pDexFile);
pDvmDex->isMappedReadOnly = false;
*ppDvmDex = pDvmDex;
--snip--
}
/*
* Parse an optimized or unoptimized .dex file sitting in memory. This is
* called after the byte-ordering and structure alignment has been fixed up.
*
* On success, return a newly-allocated DexFile.
*/
DexFile* dexFileParse(const u1* data, size_t length, int flags)
疑問:
1.為什麽沒有直接opt前dump的文章?為什麽要在rewriteDex後,Hook 驗證之
Dalvik源碼閱讀筆記(一)