Dex文件里类定义
当在虚拟机里加载Dex文件后,这个文件的数据已经读取到内存里,能不能马上使用呢?能不能使用里面的类呢?显然是不行的,因为那些加载到内存的数据,只是储存的格式,不具备运行的条件,因此需要调用方法defineClass来定义类,才可以运行在虚拟机里。所有Java编译后的类保存在Dex文件里,使用上面介绍的方法openDexFile打开Dex文件,接着需要调用方法defineClass来定义类,其实就是
当在虚拟机里加载Dex文件后,这个文件的数据已经读取到内存里,能不能马上使用呢?能不能使用里面的类呢?显然是不行的,因为那些加载到内存的数据,只是储存的格式,不具备运行的条件,因此需要调用方法defineClass来定义类,才可以运行在虚拟机里。所有Java编译后的类保存在Dex文件里,使用上面介绍的方法openDexFile打开Dex文件,接着需要调用方法defineClass来定义类,其实就是调用原生的函数Dalvik_dalvik_system_DexFile_defineClass, 类的加载流程如下:
1)方法openDexFile里通过dvmDexFileOpenFromFd函数调用dexFileParse函数,分析Dex文件里每个类名称和类的代码所在索引,然后dexFileParse调用函数dexParseOptData来把类名称写对象pDexFile->pClassLookup里面,当然也更新了索引。
2)Dalvik_dalvik_system_DexFile_defineClass函数调用dvmDefineClass函数。
3)dvmDefineClass函数调用findClassNoInit来进类的初始化。
4)findClassNoInit调用dexFindClass找到类名称在文件中位置。
5)findClassNoInit调用loadClassFromDex从文件加载。
6)loadClassFromDex调用dexGetClassData函数获取类的索引地址。
7)loadClassFromDex调用loadClassFromDex0获取类接口、类成员变量、类的方法,这样就完成类从Dex文件里加载。
下面来依次分析调用的函数代码,理解其中细节,以及相应的代码,以便更加深入,达到修改代码的水平,Dalvik_dalvik_system_DexFile_defineClass函数代码如下:
staticvoid Dalvik_dalvik_system_DexFile_defineClass(const u4* args,
JValue* pResult)
{
StringObject* nameObj = (StringObject*)args[0];
这行代码是获取输入需要加载的类对象。
Object* loader = (Object*) args[1];
这行代码是获取类加载器对象。
int cookie = args[2];
这行代码是获取打开的Dex文件对象。
Object* pd = (Object*) args[3];
这行代码是获取保护域权限。
ClassObject* clazz = NULL;
DexOrJar* pDexOrJar = (DexOrJar*) cookie;
这行代码转换为Dex文件对象。
DvmDex* pDvmDex;
char* name;
char* descriptor;
name = dvmCreateCstrFromString(nameObj);
descriptor = dvmDotToDescriptor(name);
LOGV("--- Explicit class load '%s'0x%08x\n", descriptor, cookie);
free(name);
这段代码是把类名称对象转换为C字符串,然后从C字符串转换为类描述符。
if (!validateCookie(cookie))
RETURN_VOID();
这段代码是检查是否合法的Dex文件对象。
if (pDexOrJar->isDex)
pDvmDex =dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
else
pDvmDex =dvmGetJarFileDex(pDexOrJar->pJarFile);
这段代码是获取Dex文件对象,以便从Dex文件里获取到类数据。
/* once we load something, we can't unmapthe storage */
pDexOrJar->okayToFree = false;
clazz = dvmDefineClass(pDvmDex, descriptor,loader);
这行代码是调用函数dvmDefineClass从指定的Dex文件里,使用指定的类加载器加载指定类名称的类。
Thread* self = dvmThreadSelf();
if (dvmCheckException(self)) {
/*
* If we threw a "class not found"exception, stifle it, since the
* contract in the higher method says wesimply return null if
* the class is not found.
*/
Object* excep = dvmGetException(self);
if (strcmp(excep->clazz->descriptor,
"Ljava/lang/ClassNotFoundException;")== 0 ||
strcmp(excep->clazz->descriptor,
"Ljava/lang/NoClassDefFoundError;")== 0)
{
dvmClearException(self);
}
clazz = NULL;
}
这段代码是检查是否加载类的过程有异常发生。
/*
* Set the ProtectionDomain -- do we needthis to happen before we
* link the class and make it available? Ifso, we need to pass it
* through dvmDefineClass (and figure outsome other
* stuff, like where it comes from forbootstrap classes).
*/
if (clazz != NULL) {
//LOGI("SETTING pd '%s' to %p\n",clazz->descriptor, pd);
dvmSetFieldObject((Object*) clazz,gDvm.offJavaLangClass_pd, pd);
}
这段代码是设置类的保护域。
free(descriptor);
RETURN_PTR(clazz);
这行代码返回已经加载的类代码。
}
接着来分析函数dvmDefineClass,它的代码如下:
ClassObject*dvmDefineClass(DvmDex*pDvmDex, constchar*descriptor,
Object*classLoader)
{
这个函数输入的参数有三个,第一个pDvmDex参数是表示Dex文件对象,第二个descriptor参数是需要加载类名称,第三个classLoader参数是类的加载对象。
assert(pDvmDex !=NULL);
returnfindClassNoInit(descriptor, classLoader, pDvmDex);
这行代码调用函数findClassNoInit继续初始化。
}
接着下来分析函数findClassNoInit,它的代码如下:
staticClassObject*findClassNoInit(constchar*descriptor, Object*loader,
DvmDex*pDvmDex)
{
这个函数输入的参数有三个,第一个pDvmDex参数是表示Dex文件对象,第二个descriptor参数是需要加载类名称,第三个classLoader参数是类的加载对象。
Thread*self = dvmThreadSelf();
这行代码是获取当前加载类代码的线程。
ClassObject*clazz;
#ifdefWITH_PROFILER
bool profilerNotified = false;
#endif
if(loader != NULL) {
LOGVV("####findClassNoInit(%s,%p,%p)\n",descriptor, loader,
pDvmDex->pDexFile);
}
这段代码输出,当加载类对象不为空时,就调试输出加载类的名称、类的加载器和Dex文件对象。
/*
* We don't expect anexception to be raised at this point. The
* exception handling code isgood about managing this. This *can*
* happen if a JNI lookupfails and the JNI code doesn't do any
* error checking before doinganother class lookup, so we may just
* want to clear this andrestore it on exit. If we don't, some kinds
* of failures can't bedetected without rearranging other stuff.
*
* Most often when we hit thissituation it means that something is
* broken in the VM or in JNIcode, so I'm keeping it in place (and
* making it an informativeabort rather than an assert).
*/
if(dvmCheckException(self)) {
LOGE("Classlookup %s attempedwhile exception %s pending\n",
descriptor,dvmGetException(self)->clazz->descriptor);
dvmDumpAllThreads(false);
dvmAbort();
}
这段代码处理有异常的情况出现,把异常先处理掉。
clazz= dvmLookupClass(descriptor, loader, true);
if(clazz == NULL) {
constDexClassDef*pClassDef;
这段代码是调用函数dvmLookupClass在已经加载的类里查找是否已经存在,如果已经存在,就直接使用就可以了。否则,就需要从Dex文件里读取出来。
#ifdefWITH_PROFILER
dvmMethodTraceClassPrepBegin();
profilerNotified = true;
#endif
#ifLOG_CLASS_LOADING
u8 startTime =dvmGetThreadCpuTimeNsec();
#endif
if(pDvmDex == NULL) {
assert(loader ==NULL); /* shouldn't be hereotherwise */
pDvmDex =searchBootPathForClass(descriptor, &pClassDef);
这段代码是当判断输入的参数Dex文件为空时,说明需要从系统缺省的库目录里加载,就是调用searchBootPathForClass函数去查找相应的类,并返回这个类的Dex文件对象。
} else{
pClassDef =dexFindClass(pDvmDex->pDexFile,descriptor);
这行代码是当指定从Dex文件加载时,就调用函数dexFindClass从文件查找相应的类。
}
if(pDvmDex == NULL || pClassDef == NULL) {
if(gDvm.noClassDefFoundErrorObj!= NULL) {
/*usual case -- use prefabricatedobject */
dvmSetException(self,gDvm.noClassDefFoundErrorObj);
这段代码是判断是否加载出错,如果出错就设置异常返回。
} else{
/*dexoptcase -- can't guarantee prefab(core.jar) */
dvmThrowExceptionWithClassMessage(
"Ljava/lang/NoClassDefFoundError;",descriptor);
}
这里都没有办法加载到指定类出错返回。
gotobail;
}
/*found a match, try to load it */
clazz =loadClassFromDex(pDvmDex, pClassDef, loader);
这行代码是已经发现合适的类,并且找到相应的Dex文件时,就可以调用函数loadClassFromDex来加载类到内存,并准备好可以运行的状态。
if(dvmCheckException(self)) {
/*class was found but had issues */
dvmReleaseTrackedAlloc((Object*)clazz, NULL);
gotobail;
}
这段代码是当从Dex文件时加载类出错时返回。
/*
* Lock the class while welink it so other threads must wait for us
* to finish. Set the"initThreadId" so we can identify recursive
* invocation.
*/
dvmLockObject(self,(Object*)clazz);
clazz->initThreadId= self->threadId;
这段代码是锁住别的线程访问,然后设置这个类的初始化线程的ID,以便防止递归调用时可以判断出来。
/*
* Add to hash table solookups succeed.
*
* [Are circularreferences possible when linking a class?]
*/
下面这段代码是添加新加载的类到hash表里,以便后面查找快速。
assert(clazz->classLoader== loader);
if(!dvmAddClassToHash(clazz)) {
这行代码是调用dvmAddClassToHash函数来添加新加载的类clazz到hash表,也就是保存在gDvm.loadedClasses里。但是添加到hash表里,也不是每次成功的,因为加载类是多线程进行的,可能同一个类在不同的线程里加载了,另一个线程加载就不成功,因此dvmAddClassToHash函数会返回插入不成功,这时就需要进行下面的处理了。
/*
* Another thread musthave loaded the class after we
* started but beforewe finished. Discard what we've
* done and leave somehints for the GC.
*
* (Yes, thishappens.)
*/
//LOGW("WOW:somebody loaded %s simultaneously\n", descriptor);
clazz->initThreadId= 0;
dvmUnlockObject(self,(Object*)clazz);
这段代码是发现已经有别的线程加载同一个类,就设置这个类初始化的线程为空,然后释放这个线程锁。
/*Let the GC free the class.
*/
assert(clazz->obj.clazz== gDvm.unlinkedJavaLangClass);
dvmReleaseTrackedAlloc((Object*)clazz, NULL);
这段代码释放分配类占用的内存。
/*Grab the winning class.
*/
clazz =dvmLookupClass(descriptor, loader, true);
assert(clazz != NULL);
gotogot_class;
这段代码是从已经加载的类引用一份就可以返回给调用函数了。
接着下来,来分析怎么样加载方法的代码,loadMethodFromDex函数的代码如下:
staticvoidloadMethodFromDex(ClassObject*clazz, const DexMethod*pDexMethod,
Method*meth)
{
这个函数传入三个参数,第一个参数clazz是类相关信息;第二个参数pDexMethod是要加载的方法相关信息;第三个参数meth是指向加载的方法,也就是可以运行的代码。
DexFile*pDexFile = clazz->pDvmDex->pDexFile;
这行代码是获取Dex文件。
constDexMethodId*pMethodId;
constDexCode*pDexCode;
pMethodId =dexGetMethodId(pDexFile, pDexMethod->methodIdx);
这行代码是从Dex文件里通过索引获取到方法的ID。
meth->name= dexStringById(pDexFile, pMethodId->nameIdx);
这行代码是获取方法的名称。
dexProtoSetFromMethodId(&meth->prototype,pDexFile, pMethodId);
meth->shorty= dexProtoGetShorty(&meth->prototype);
meth->accessFlags= pDexMethod->accessFlags;
meth->clazz= clazz;
meth->jniArgInfo= 0;
这段代码是设置方法的属性。
if(dvmCompareNameDescriptorAndMethod("finalize","()V",meth) == 0) {
SET_CLASS_FLAG(clazz,CLASS_ISFINALIZABLE);
}
这段代码是判断是否最后退出函数,如果是退出函数就设置标志。
pDexCode =dexGetCode(pDexFile, pDexMethod);
这行代码是获取方法执行代码,如果是本地方法或者抽像函数返回空。
if(pDexCode != NULL) {
/*integer constants, copy over for faster access */
meth->registersSize= pDexCode->registersSize;
meth->insSize= pDexCode->insSize;
meth->outsSize= pDexCode->outsSize;
/*pointer to code area */
meth->insns= pDexCode->insns;
这段代码是从Dex文件里获取到方法的代码,保存相应的方法结构里,以便后面使用。
} else{
/*
* We don't have a DexCodeblock, but we still want to know how
* much space is neededfor the arguments (so we don't have to
* compute it later). Wealso take this opportunity to compute
* JNI argument info.
*
* We do this for abstractmethods as well, because we want to
* be able to substituteour exception-throwing "stub" in.
*/
intargsSize = dvmComputeMethodArgsSize(meth);
这行代码是计算方法参数大小。
if(!dvmIsStaticMethod(meth))
argsSize++;
这段代码是静态方法需要增加参数。
meth->registersSize= meth->insSize= argsSize;
assert(meth->outsSize== 0);
assert(meth->insns== NULL);
if(dvmIsNativeMethod(meth)) {
meth->nativeFunc= dvmResolveNativeMethod;
meth->jniArgInfo= computeJniArgInfo(&meth->prototype);
}
这段代码是处理本地方法的调用。
}
}
到这里,就可以把每个方法的代码解释出来,获取到方法所有的相关信息,这样就可以备运行条件了。
更多推荐
所有评论(0)