dalvik1

概述

基于https://blog.csdn.net/luoshengyang/article/details/8852432的分析整理。
涉及dalvik启动过程,运行过程

目标为搞懂虚拟机native中的本质。现在基本都不用dalvik了,在我的8.0机子下persist.sys.dalvik.vm.lib.2=libart.so 表示用art虚拟机,dalvik已经完全不见踪影。

前提杂记

linux下分析:
vim:
:sp可直接分屏,ctrl+w切换分屏,ctrl+w+o只留选中的
:/用来搜索 按n下一个、N上一个 光标选中单词shift+*直接搜索该单词

基于栈的虚拟机字节更小,指令所需多;基于寄存器的字节更长,缓存命中小
RISC精简指令集操作更原子,CISC复杂指令集操作更大,二者都是栈与寄存器运行结构。

android内部C++代码大量使用智能指针管理对象声明周期,防止内存释放问题。开发使用java自动回收

每个进程都有dalvik实例,每个应用进程由Zygote fork出,Zygote进程是由init进程启动,在系统启动时加载所需。
这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android应用程序进程的创建过程很快,而且由于所有的Android应用程序进程都共享同一套Java核心库而节省了内存空间。

dalvik启动过程

Linux中所有进程基于init进程,android中init创建名为zygote的进程,这个zygote进程要执行的程序是/system/bin/app_process。其主函数为frameworks/base/cmds/app_process/app_main.cpp中的main
之后的调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
App_main.main
AndroidRuntime.start
JniInvocation.Init
startVm 在这里启动虚拟机
JNI_CreateJavaVM dalvik/vm/Jni.c中
dvmCreateJNIEnv
dvmStartup dalvik/vm/Init.c中
dvmInitZygote
startReg
ZygoteInit.main java类,在这里进入java层 env->CallStaticVoidMethod(startClass, startMeth, strArray)
registerZygoteSocket
preload
startSystemServer
runSelectLoop

注意这个是按函数划分的,只能表现调用栈,方法实现的位置与该图无关。而uml的时序图可以表现出方法的实现位置(对象、类、所属文件),在同类内的方法调用上镶嵌一个条即可表现出调用栈。

开始说明源码:

startVm前用

1
2
3
JniInvocation jni_invocation;
//load libart.so 初始化JniInvocation::JNI_CreateJavaVM_接口调用的指针
jni_invocation.Init(NULL);

JniInvocation统一三个接口

1
2
3
4
5
6
7
8
9
10
11
jint JniInvocation::JNI_GetDefaultJavaVMInitArgs(void* vmargs) {
return JNI_GetDefaultJavaVMInitArgs_(vmargs);
}
jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
return JNI_CreateJavaVM_(p_vm, p_env, vm_args);
}
jint JniInvocation::JNI_GetCreatedJavaVMs(JavaVM** vms, jsize size, jsize* vm_count) {
return JNI_GetCreatedJavaVMs_(vms, size, vm_count);
}

这三个都是调用函数指针,函数指针正是初始化在JniInvocation::Init():
其用dlopen加载默认的虚拟机so(libart.so或libdvm.so),并用dlsym加载函数的实现

AndroidRuntime::startVm 设置参数,调用JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs)

dalvik的JNI_CreateJavaVM在dalvik/vm/Jni.cpp中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
memset(pVM, 0, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface; 给予函数表
pVM->envList = pEnv; 给予jni环境
将参数vm_args所描述的Dalvik虚拟机启动选项拷贝到变量argv所描述的一个字符串数组中去,并且调用函数dvmStartup来初始化前面所创建的Dalvik虚拟机实例。
gDvm.vmList = (JavaVM*) pVM;将pvm传入全局,这个属于进程的全局
pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL); 为当前线程创建和初始化一个JNI环境,即一个JNIEnvExt对象,这个属于线程,用于接下来执行使用
gDvm.initializing = true; dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) 初始化前面所创建的Dalvik虚拟机实例,功能正式启用???
dvmChangeStatus(NULL, THREAD_NATIVE);改变虚拟机状态为native
输出参数p_vm和p_env返回给调用者

每一个Dalvik虚拟机实例还有一个JNI环境列表,保存在对应的JavaVMExt对象的成员变量envList中。注意,JavaVMExt对象的成员变量envList描述的是一个JNIEnvExt列表,其中,每一个Attach到Dalvik虚拟机中去的线程都有一个对应的JNIEnvExt,用来描述它的JNI环境。有了这个JNI环境之后,我们才可以在Java函数和C/C++函数之间互相调用。

dvmCreateJNIEnv在 dalvik/vm/Jni.c 中
注意这里的JavaVM 与 JNIEnv 都是ndk编程中的,Ext表示拓展,可以强制转化为ndk中的。一般是拓展的父类。在c中实现为同结构体内存布局强转。

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
JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
JNIEnvExt* newEnv;
......
newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
newEnv->funcTable = &gNativeInterface; jni环境的本地接口表
newEnv->vm = vm; jni的宿主机
......
if (self != NULL) {
dvmSetJniEnvThreadId((JNIEnv*) newEnv, self); 参数self描述的是前面创建的JNIEnvExt对象要关联的线程
assert(newEnv->envThreadId != 0);
} else {
/* make it obvious if we fail to initialize these later */
当参数self的值等于NULL的时候,就表示前面的JNIEnvExt对象是要与主线程关联的,但是要等到后面再关联,因为现在用来描述主线程的Thread对象还没有准备好。通过将一个JNIEnvExt对象的成员变量envThreadId和self的值分别设置为0x77777775和0x77777779来表示它还没有与线程关联。
newEnv->envThreadId = 0x77777775;
newEnv->self = (Thread*) 0x77777779;
}
......
/* insert at head of list */
在一个Dalvik虚拟机里面,可以运行多个线程。所有关联有JNI环境的线程都有一个对应的JNIEnvExt对象,这些JNIEnvExt对象相互连接在一起保存在用来描述其宿主Dalvik虚拟机的一个JavaVMExt对象的成员变量envList中。因此,前面创建的JNIEnvExt对象需要连接到其宿主Dalvik虚拟机的JavaVMExt链表中去。
一个dalvik有多个线程,每个线程一个jnienv,并用链表加到javavm中记录。
newEnv->next = vm->envList;
assert(newEnv->prev == NULL);
if (vm->envList == NULL) // rare, but possible
vm->envList = newEnv;
else
vm->envList->prev = newEnv;
vm->envList = newEnv;
......
return (JNIEnv*) newEnv;

在dalvik\libnativehelper\include\nativehelper\jni.h文件中的定义了JNINativeInterface接口,
在这个文件中static const struct JNINativeInterface gNativeInterface = {…}将函数指针传入JNINativeInterface结构体的实例,该结构体中都是函数指针如jclass (*FindClass)(JNIEnv*, const char*);
jclass 类型就是一个空类* 其变量表示空类的对象的指针。
举一个static jclass FindClass(JNIEnv* env, const char* name) 的实现,在dalvik/vm/Jni.c 中,调用了dvmFindClassNoInit获取jvm的ClassObject* 同时传入的还有classLoader,这些函数的实现是统一在此的。

javaVM的定义:

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
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
typedef _JavaVM JavaVM;
struct _JavaVM {
const struct JNIInvokeInterface* functions; ////JNI接口
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
};
static const struct JNIInvokeInterface gInvokeInterface = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon,
};

pVM->funcTable = &gInvokeInterface; 填入函数指针
所以gDvmJni.jniVm = (JavaVM*) pVM;
所以JavaVM就是函数表…Ext是其拓展

dvmStartup:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
std::string dvmStartup(int argc, const char* const argv[],
bool ignoreUnrecognized, JNIEnv* pEnv)
setCommandLineDefaults();设置默认参数
cc = dvmProcessOptions(argc, argv, ignoreUnrecognized);根据这些选项值来设置Dalvik虚拟机的属性
/*
* Initialize components.
*/
if (!dvmAllocTrackerStartup()) 初始化Davlik虚拟机的对象分配记录子模块
goto fail;
if (!dvmGcStartup()) 初始化Davlik虚拟机的垃圾收集( GC)子模块
goto fail;
if (!dvmThreadStartup()) 用来初始化Davlik虚拟机的线程列表、为主线程创建一个Thread对象以及为主线程初始化执行环境。 这个对象的C++层的
goto fail; //Davlik虚拟机中的每一个线程均用一个Thread结构体来描述,这些Thread结构体组织在一个列表中,因此,这里要先对它进行初始化。
if (!dvmInlineNativeStartup()) 用来初始化Davlik虚拟机的内建Native函数表。这些内建Native函数主要是针对java.Lang.String、java.Lang.Math、java.Lang.Float和java.Lang.Double类的,用来替换这些类的某些成员函数原来的实现(包括Java实现和Native实现)。
goto fail;
if (!dvmVerificationStartup()) 用来初始化Dex文件验证器。
goto fail;
if (!dvmRegisterMapStartup()) 用来初始化寄存器映射集(Register Map)子模块
goto fail;
if (!dvmInstanceofStartup()) 用来初始化instanceof操作符子模块。
goto fail;
if (!dvmClassStartup()) 用来初始化启动类加载器(Bootstrap Class Loader),同时还会初始化java.lang.Class类。!!开始java类加载
goto fail;
if (!dvmThreadObjStartup()) 用来加载与线程相关的类,即java.lang.Thread、java.lang.VMThread和java.lang.ThreadGroup。
goto fail;
if (!dvmExceptionStartup()) 用来加载与异常相关的类,即java.lang.Throwable、java.lang.RuntimeException、java.lang.StackOverflowError...
goto fail;
if (!dvmStringInternStartup()) 用来初始化java.lang.String类内部私有一个字符串池,这样当Dalvik虚拟机运行起来之后,我们就可以调用java.lang.String类的成员函数intern来访问这个字符串池里面的字符串
goto fail;
if (!dvmNativeStartup()) 用来初始化Native Shared Object库加载表,也就是SO库加载表。这个加载表是用来描述当前进程有哪些SO文件已经被加载过了。
goto fail;
if (!dvmInternalNativeStartup()) 用来初始化一个内部Native函数表。所有需要直接访问Dalvik虚拟机内部函数或者数据结构的Native函数都定义在这张表中,因为它们如果定义在外部的其它SO文件中,就无法直接访问Dalvik虚拟机的内部函数或者数据结构。
goto fail;
if (!dvmJniStartup()) 用来初始化全局引用表,以及加载一些与Direct Buffer相关的类
goto fail;
if (!dvmReflectStartup()) 用来加载反射相关的类
goto fail;
if (!dvmProfilingStartup()) 用来初始化Dalvik虚拟机的性能分析子模块
goto fail;
/*
* Miscellaneous class library validation.
*/
if (!dvmValidateBoxClasses()) 用来验证Dalvik虚拟机中存在相应的装箱类
goto fail;
/*
* Do the last bits of Thread struct initialization we need to allow
* JNI calls to work.
*/
if (!dvmPrepMainForJni(pEnv)) 用来准备主线程的JNI环境,即将在dvmThreadStartup中为主线程创建的Thread对象与在前面创建的JNI环境关联起来。
goto fail;
/*
* Register the system native methods, which are registered through JNI.
*/
if (!registerSystemNatives(pEnv)) 调用另外一个函数jniRegisterSystemMethods,后者接着又调用了函数registerCoreLibrariesJni来为Java核心类注册JNI方法
goto fail;
/*
* Do some "late" initialization for the memory allocator. This may
* allocate storage and initialize classes.
*/
if (!dvmCreateStockExceptions()) 用来预创建一些与内存分配有关的异常对象
goto fail;
/*
* At this point, the VM is in a pretty good state. Finish prep on
* the main thread (specifically, create a java.lang.Thread object to go
* along with our Thread struct). Note we will probably be executing
* some interpreted class initializer code in here.
*/
if (!dvmPrepMainThread()) 用来为主线程创建一个java.lang.ThreadGroup对象、一个java.lang.Thread对角和java.lang.VMThread对象。这些Java对象和在前面创建的C++层Thread对象关联一起,共同用来描述Dalvik虚拟机的主线程。
goto fail;
/*
* Make sure we haven't accumulated any tracked references. The main
* thread should be starting with a clean slate.
*/
if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0) 用来确保主线程当前不引用有任何Java对象,这是为了保证主线程接下来以干净的方式来执行程序入口。
{
LOGW("Warning: tracked references remain post-initialization\n");
dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
}
/* general debugging setup */
if (!dvmDebuggerStartup()) 用来初始化Dalvik虚拟机的调试环境
goto fail
/*
* Init for either zygote mode or non-zygote mode. The key difference
* is that we don't start any additional threads in Zygote mode.
*/
if (gDvm.zygote) { 检查Dalvik虚拟机是否指定了-Xzygote启动选项。如果指定了的话,那么就说明当前是在Zygote进程中启动Dalvik虚拟机,因此,接下来就会调用函数dvmInitZygote来执行最后一步初始化工作
if (!dvmInitZygote())
goto fail;
} else {
if (!dvmInitAfterZygote())
goto fail;
}
......
return 0;
fail:
dvmShutdown();
return 1;

static bool dvmInitZygote(void):在dalvik/vm/Init.c中
setpgid(0,0);
调用了系统调用setpgid来设置当前进程,即Zygote进程的进程组ID。注意,在调用setpgid的时候,传递进去的两个参数均为0,这意味着Zygote进程的进程组ID与进程ID是相同的,也就是说,Zygote进程运行在一个单独的进程组里面。

AndroidRuntime.startReg :

函数startReg来注册一些Android核心类的JNI方法。

总结:

1. 创建了一个Dalvik虚拟机实例;

2. 加载了Java核心类及其JNI方法;

3. 为主线程的设置了一个JNI环境;

4. 注册了Android核心类的JNI方法。

Zygote进程为Android系统准备好了一个Dalvik虚拟机实例,以后Zygote进程在创建Android应用程序进程的时候,就可以将它自身的Dalvik虚拟机实例复制到新创建Android应用程序进程中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct JNIEnvExt {
const struct JNINativeInterface* funcTable; /* must be first */
const struct JNINativeInterface* baseFuncTable;
u4 envThreadId;
Thread* self;
/* if nonzero, we are in a "critical" JNI call */
int critical;
struct JNIEnvExt* prev;
struct JNIEnvExt* next;
};
虚拟机
struct JavaVMExt {
const struct JNIInvokeInterface* funcTable; /* must be first */
const struct JNIInvokeInterface* baseFuncTable;
/* head of list of JNIEnvs associated with this VM */
JNIEnvExt* envList;
pthread_mutex_t envListLock;
};

可以转化为一般的JavaVM和JNIEnv是因为那俩个类(C++)或直接就是JNIInvokeInterface指针(C)都是第一个元素就是函数表指针。方法为代码不占struct的对象存储。

运行过程

1
2
3
4
5
6
调用java类:
JNIEnv.CallStaticVoidMethod
functions->CallStaticVoidMethodV(this, clazz, methodID, args); 这个是函数表指针之一,函数表指针的实现当然在Jni.cpp。 c++的类包装中会转化下参数再调用V版c在原版中再转化
CallStaticVoidMethodV (定义在jni.c中的实现)
dvmCallMethodV dalvik/vm/interp/Stack.c
dvmInterpret(self, method, pResult); dalvik/vm/interp/Interp.c
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
static _ctype CallStaticVoidMethodV(JNIEnv* env, jclass jclazz,
jmethodID methodID, va_list args)
{
UNUSED_PARAMETER(jclazz);
JNI_ENTER();
JValue result;
dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);
if (_isref && !dvmCheckException(_self))
result.l = addLocalReference(env, result.l);
JNI_EXIT();
return _retok;
}
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
......
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)(self->curFrame, pResult, method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
......
}
void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
{
InterpState interpState;
......
/*
* Initialize working state.
*
* No need to initialize "retval".
*/
interpState.method = method;
interpState.fp = (u4*) self->curFrame;
interpState.pc = method->insns;
......
typedef bool (*Interpreter)(Thread*, InterpState*);
Interpreter stdInterp;
if (gDvm.executionMode == kExecutionModeInterpFast)
stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
else if (gDvm.executionMode == kExecutionModeJit)
/* If profiling overhead can be kept low enough, we can use a profiling
* mterp fast for both Jit and "fast" modes. If overhead is too high,
* create a specialized profiling interpreter.
*/
stdInterp = dvmMterpStd;
#endif
else
stdInterp = dvmInterpretStd;
change = true;
while (change) {
switch (interpState.nextMode) {
case INTERP_STD:
LOGVV("threadid=%d: interp STD\n", self->threadId);
change = (*stdInterp)(self, &interpState);
break;
case INTERP_DBG:
LOGVV("threadid=%d: interp DBG\n", self->threadId);
change = dvmInterpretDbg(self, &interpState);
break;
default:
dvmAbort();
}
}
*pResult = interpState.retval;
......
}

选择dalvik解释器,函数dvmInterpretStd在执行之前,需要知道解释器的当前状态,也就是它所要执行的Java函数及其指令入口,以及当前要执行的线程的堆栈。这些信息都用一个InterpState结构体来描述。其中,这个InterpState结构体的成员变量method描述的是要执行的Java函数,成员变量fp描述的是要执行的线程的当前堆栈帧,成员变量pc描述的是要执行的Java函数的入口点。

之后到了指令执行:

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
58
59
60
61
62
63
64
65
66
67
68
#define INTERP_FUNC_NAME dvmInterpretStd
......
bool INTERP_FUNC_NAME(Thread* self, InterpState* interpState)
{
......
DvmDex* methodClassDex; // curMethod->clazz->pDvmDex
JValue retval;
/* core state */
const Method* curMethod; // method we're interpreting
const u2* pc; // program counter
u4* fp; // frame pointer
u2 inst; // current instruction
......
/* copy state in */
curMethod = interpState->method;
pc = interpState->pc;
fp = interpState->fp;
retval = interpState->retval; /* only need for kInterpEntryReturn? */
methodClassDex = curMethod->clazz->pDvmDex;
......
while (1) {
......
/* fetch the next 16 bits from the instruction stream */
inst = FETCH(0);
switch (INST_INST(inst)) {
......
HANDLE_OPCODE(OP_INVOKE_DIRECT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
GOTO_invoke(invokeDirect, false);
OP_END
......
HANDLE_OPCODE(OP_RETURN /*vAA*/)
vsrc1 = INST_AA(inst);
......
retval.i = GET_REGISTER(vsrc1);
GOTO_returnFromMethod();
OP_END
......
}
}
......
/* export state changes */
interpState->method = curMethod;
interpState->pc = pc;
interpState->fp = fp; 成员变量fp描述的是要执行的线程的当前堆栈帧
/* debugTrackedRefStart doesn't change */
interpState->retval = retval; /* need for _entryPoint=ret */
interpState->nextMode =
(INTERP_TYPE == INTERP_STD) ? INTERP_DBG : INTERP_STD;
......
return true;
}

与想象中一致,使用while switch逐个执行指令
smali语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# direct methods
.method public constructor <init>()V
.locals 1
.prologue
.line 25
invoke-direct {p0}, Landroid/os/Binder;-><init>()V
.line 20
const/4 v0, 0x0
iput-object v0, p0, Lcom/qihoo360/replugin/Entry$Stub;->mRemote:Landroid/os/IBinder;
.line 26
const-string v0, "com.qihoo360.loader2.IPlugin"
invoke-virtual {p0, p0, v0}, Lcom/qihoo360/replugin/Entry$Stub;->attachInterface(Landroid/os/IInterface;Ljava/lang/String;)V
.line 27
return-void
.end method

注意参数、变量都是用寄存器存储使用的,调用函数没有显示的栈帧,在虚拟机中每个函数都对使用到的变量进行分配与回收,对每个线程保存自己的环境比如函数返回:
fp = saveArea->prevFrame
pc = saveArea->savedPc;

可见基本等同于模仿了一个cpu,不过一个指令的功能更为强大,native代码加载于内存中

1
2
3
4
jmethodID methodID 看jni.h头文件的定义中jmethodID就是一个指针:
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */

相当于一个void *。之后被强制化为(Method*)methodID 可见这个指针应该指向的是一个dex方法描述,内有其运行地址,应该是解析dex后保存在某处的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interpState.method = method;
interpState.fp = (u4*) self->curFrame;
interpState.pc = method->insns;
/* core state */
const Method* curMethod; // method we're interpreting
const u2* pc; // program counter
u4* fp; // frame pointer
u2 inst; // current instruction
......
/* copy state in */
curMethod = interpState->method;
pc = interpState->pc;
fp = interpState->fp;
retval = interpState->retval;

执行。

System.loadLibrary
Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个JNI方法,那么就会直接跳到它的地址去执行。也就是说,JNI方法是直接在本地操作系统上执行的

在Dalvik虚拟机中,类加载器除了知道它要加载的类所在的文件路径之外,还知道该类所属的APK用来保存so文件的路径。因此,给定一个so文件名称,一个类加载器可以判断它是否存在自己的so文件目录中。

因此dex的解析加载过程十分重要,为逆向重点。
再来看下dvmClassStartup

https://www.jianshu.com/p/92c2f732d2d6?from=timeline