Android低版本(4.x)无法通过app_process启动java进程问题详解

最近遇到一个奇怪的事情,公司有一块业务需要在Android上通过启动一个普通的java进程,但是从后台统计数据上来看在Android4.x上进程无法启动所以找台Nexux4 4.2.2的手机来试果然会崩溃,崩溃日志如下

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
D/AndroidRuntime( 3877): >>>>>> AndroidRuntime START com.android.internal.os.RuntimeInit <<<<<<
D/AndroidRuntime( 3877): CheckJNI is OFF
D/dalvikvm( 3877): Trying to load lib libjavacore.so 0x0
D/dalvikvm( 3877): Added shared lib libjavacore.so 0x0
D/dalvikvm( 3877): Trying to load lib libnativehelper.so 0x0
D/dalvikvm( 3877): Added shared lib libnativehelper.so 0x0
D/dalvikvm( 3877): No JNI_OnLoad found in libnativehelper.so 0x0, skipping init
E/dalvikvm( 3877): Dex cache directory isn't writable: /data/dalvik-cache
I/dalvikvm( 3877): Unable to open or create cache for /data/data/com.mjar.test/cache/app@data.apk (/data/dalvik-cache/data@data@com.mjar.test@cache@app@data.apk@classes.dex)
E/appproc ( 3877): ERROR: could not find class 'cmd.Main'
E/dalvikvm( 3877): JNI posting fatal error: Native registration unable to find class 'android/debug/JNITest'; aborting...
I/dalvikvm( 3877): "main" prio=5 tid=1 NATIVE
I/dalvikvm( 3877): | group="main" sCount=0 dsCount=0 obj=0x415b7ca8 self=0x6d455010
I/dalvikvm( 3877): | sysTid=3877 nice=0 sched=0/0 cgrp=default handle=1074467156
I/dalvikvm( 3877): | state=R schedstat=( 0 0 0 ) utm=19 stm=13 core=0
I/dalvikvm( 3877): #00 pc 0000132e /system/lib/libcorkscrew.so (unwind_backtrace_thread+29)
I/dalvikvm( 3877): #01 pc 00060652 /system/lib/libdvm.so (dvmDumpNativeStack(DebugOutputTarget const*, int)+33)
I/dalvikvm( 3877): #02 pc 00054640 /system/lib/libdvm.so (dvmDumpThreadEx(DebugOutputTarget const*, Thread*, bool)+395)
I/dalvikvm( 3877): #03 pc 000546ae /system/lib/libdvm.so (dvmDumpThread(Thread*, bool)+25)
I/dalvikvm( 3877): #04 pc 000490c0 /system/lib/libdvm.so
I/dalvikvm( 3877): #05 pc 00001fa8 /system/lib/libnativehelper.so (jniRegisterNativeMethods+39)
I/dalvikvm( 3877): #06 pc 0004cd22 /system/lib/libandroid_runtime.so
I/dalvikvm( 3877): #07 pc 0004d010 /system/lib/libandroid_runtime.so (android::AndroidRuntime::startReg(_JNIEnv*)+23)
I/dalvikvm( 3877): #08 pc 0004da60 /system/lib/libandroid_runtime.so (android::AndroidRuntime::start(char const*, char const*)+183)
I/dalvikvm( 3877): #09 pc 0000105a /system/bin/app_process
I/dalvikvm( 3877): #10 pc 0000e348 /system/lib/libc.so (__libc_init+47)
I/dalvikvm( 3877): at dalvik.system.NativeStart.main(Native Method)
I/dalvikvm( 3877): at dalvik.system.NativeStart.main(Native Method)
I/dalvikvm( 3877):
E/dalvikvm( 3877): VM aborting

通过分析日志得知崩溃的原因是因为cmd.Main这个类没找到,但是明明在5.0以上的版本是可以执行的所以可以排除类不在dex文件里,正当百思不得其姐的时候定睛一看还有一行很关键的信息 E/dalvikvm( 3877): Dex cache directory isn't writable: /data/dalvik-cache 看样子跟这个有点关系啊!我们知道在android上加载dex文件时系统会做一些优化以加快运行速度也就是dex2oat的过程优化后的odex文件就存储在/data/dalvik-cache这个目录中,因为我们加载自定义的dex文件所以在执行dex2oat的这个操作也是以普通应用的uid去执行的所以理所当然的会没有权限写入这个目录,但是不写入这个目录dex文件不就无法运行起来到这似乎陷入了一个僵局,没办法只能翻翻系统源码看看有没有什么可操作的空间了。

  • 我这里以Android4.2.2的源码抓重要的说 感兴趣的同学可以完整的跟一下app_process启动进程的整个流程

经过app_process一系列的初始化最终会调到DexFile.java去加载原始的dex文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class DexFile{

public DexFile(File file) throws IOException {
this(file.getPath());
}

public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}

/*
* Open a DEX file. The value returned is a magic VM cookie. On
* failure, an IOException is thrown.
*/
native private static int openDexFile(String sourceName, String outputName,int flags, throws IOException;
}

经过DexFile的构造器调用最终会调到openDexFile这个native函数,这个native函数实现位于dalvik_system_DexFile.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFile },
{ "openDexFile", "([B)I",
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ "closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile },
{ "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClass },
{ "getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList },
{ "isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};

可以看到openDexFile这个函数在jni层对应的是Dalvik_dalvik_system_DexFile_openDexFile函数这个函数中其中有一段很关键的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* Try to open it directly as a DEX if the name ends with ".dex".
* If that fails (or isn't tried in the first place), try it as a
* Zip with a "classes.dex" inside.
*/
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文件后缀的则直接打开同时odex文件会直接写到/data/dalvik-cache这个目录中,这个也是直接导致我们通过app_process运行dex文件会崩溃的元凶,别着急我们继续往下看下面还有一个处理jar/zip/apk后缀的分支这个里面会不会有什么惊喜呢?跟到dvmJarFileOpen里面可以看到有这么一句

1
2
3
4
5
6
7
8
9
10
11
12
    int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
//......

/* 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);

//......
}

看到这里的注释我想大家应该的都明白了,对于apk文件处理方式有些特殊会优先在同级目录查找odex文件,所以我需要做的仅仅是把dex文件改为apk文件然后通过反射调用DexFile#loadDex函数指定第二个参数odex文件名称提前把odex文件生成出来再使用app_process去运行apk文件就可以了,不过为什么在Android5.0以上可以直接加载dex文件呢,于是我在8.0系统上又试了一下发现在运行dex文件的同时系统会自动在同级目录创建一个oat目录里面存放的正是odex文件,所以由此可以猜测当在高版本的系统上运行app_process命令如果没有权限写入/data/dalvik-cache则会在同级目录自动创建odex文件,这只是我个人的猜想并没有详细的研究源码怎么实现的感兴趣的话可以自己研究一下。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2015-2024 Kaisar
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信