一个dex加载到native内存的时候,如果不存在odex文件会首先执行dexopt的入口在dalvik/dexopt/OptMain.cpp
的main方法。这里主要分析在生成odex过程中的权限校验和对热修复的影响。
/dalvik/dexopt/OptMain.cpp
main(int argc, char* const argv[])
fromDex
/dalvik/vm/analysis/DexPrepare.cpp
dvmContinueOptimization
rewriteDex
verifyAndOptimizeClasses(pDvmDex->pDexFile, doVerify, doOpt)
verifyAndOptimizeClass(pDexFile, clazz, pClassDef, doVerify, doOpt);
所以我们找到了关键函数verifyAndOptimizeClass
,verifyAndOptimizeClass
首先校验类dvmVerifyClass(clazz)
,如果校验成功会给我们的类打上一个CLASS_ISPREVERIFIED
标志,代表该类已经被校验过了。接着,调用dvmOptimizeClass(clazz, false);
对类进行优化,简单来说这个过程把部分指令优化成内部虚拟机指令,比如:比如invoke-*
指令变成了invoke-*-quick
指令,quick指令会直接从类的vtable
获取执行,vtable
可以理解为类(包括继承自父类)的所有方法的表,关于vtable
后面会分析。
/*
* Verify and/or optimize a specific class.
*/
static void verifyAndOptimizeClass(DexFile* pDexFile, ClassObject* clazz,
const DexClassDef* pClassDef, bool doVerify, bool doOpt)
{
const char* classDescriptor;
bool verified = false;
if (clazz->pDvmDex->pDexFile != pDexFile) {
/*
* The current DEX file defined a class that is also present in the
* bootstrap class path. The class loader favored the bootstrap
* version, which means that we have a pointer to a class that is
* (a) not the one we want to examine, and (b) mapped read-only,
* so we will seg fault if we try to rewrite instructions inside it.
*/
ALOGD("DexOpt: not verifying/optimizing '%s': multiple definitions",
clazz->descriptor);
return;
}
classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
/*
* First, try to verify it.
*/
if (doVerify) {
//校验类,实际上校验类的每个方法的每个指令
if (dvmVerifyClass(clazz)) {
/*
* Set the "is preverified" flag in the DexClassDef. We
* do it here, rather than in the ClassObject structure,
* because the DexClassDef is part of the odex file.
*/
assert((clazz->accessFlags & JAVA_FLAGS_MASK) ==
pClassDef->accessFlags);
((DexClassDef*)pClassDef)->accessFlags |= CLASS_ISPREVERIFIED;
verified = true;
} else {
// TODO: log when in verbose mode
ALOGV("DexOpt: '%s' failed verification", classDescriptor);
}
}
if (doOpt) {
bool needVerify = (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||
gDvm.dexOptMode == OPTIMIZE_MODE_FULL);
if (!verified && needVerify) {
ALOGV("DexOpt: not optimizing '%s': not verified",
classDescriptor);
} else {
//类的优化
dvmOptimizeClass(clazz, false);
/* set the flag whether or not we actually changed anything */
((DexClassDef*)pClassDef)->accessFlags |= CLASS_ISOPTIMIZED;
}
}
}
dvmVerifyClass
dvmVerifyClass
会调用verifyMethod
校验每一个方法,verifyMethod->dvmVerifyCodeFlow->doCodeVerification->verifyInstruction对每个指令进行校验
。
bool dvmVerifyClass(ClassObject* clazz)
{
int i;
if (dvmIsClassVerified(clazz)) {
ALOGD("Ignoring duplicate verify attempt on %s", clazz->descriptor);
return true;
}
for (i = 0; i < clazz->directMethodCount; i++) {
if (!verifyMethod(&clazz->directMethods[i])) {
LOG_VFY("Verifier rejected class %s", clazz->descriptor);
return false;
}
}
for (i = 0; i < clazz->virtualMethodCount; i++) {
if (!verifyMethod(&clazz->virtualMethods[i])) {
LOG_VFY("Verifier rejected class %s", clazz->descriptor);
return false;
}
}
return true;
}
verifyInstruction
/dalvik/vm/analysis/CodeVerify.cpp
static bool verifyInstruction(const Method* meth, InsnFlags* insnFlags,
RegisterTable* regTable, int insnIdx, UninitInstanceMap* uninitMap,
int* pStartGuess){
...
case OP_NEW_INSTANCE:
resClass = dvmOptResolveClass(meth->clazz, decInsn.vB, &failure);
...
case OP_INVOKE_VIRTUAL:
case OP_INVOKE_VIRTUAL_RANGE:
case OP_INVOKE_SUPER:
case OP_INVOKE_SUPER_RANGE:
{
Method* calledMethod;
RegType returnType;
bool isRange;
bool isSuper;
isRange = (decInsn.opcode == OP_INVOKE_VIRTUAL_RANGE ||
decInsn.opcode == OP_INVOKE_SUPER_RANGE);
isSuper = (decInsn.opcode == OP_INVOKE_SUPER ||
decInsn.opcode == OP_INVOKE_SUPER_RANGE);
calledMethod = verifyInvocationArgs(meth, workLine, insnRegCount,
&decInsn, uninitMap, METHOD_VIRTUAL, isRange,
isSuper, &failure);
if (!VERIFY_OK(failure))
break;
returnType = getMethodReturnType(calledMethod);
setResultRegisterType(workLine, insnRegCount, returnType);
justSetResult = true;
}
break;
...
if (!VERIFY_OK(failure)) {
if (failure == VERIFY_ERROR_GENERIC || gDvm.optimizing) {
/* immediate failure, reject class */
LOG_VFY_METH(meth, "VFY: rejecting opcode 0x%02x at 0x%04x",
decInsn.opcode, insnIdx);
goto bail;
} else {
/* replace opcode and continue on */
ALOGD("VFY: replacing opcode 0x%02x at 0x%04x",
decInsn.opcode, insnIdx);
//指令替换
if (!replaceFailingInstruction(meth, insnFlags, insnIdx, failure)) {
LOG_VFY_METH(meth, "VFY: rejecting opcode 0x%02x at 0x%04x",
decInsn.opcode, insnIdx);
goto bail;
}
/* IMPORTANT: meth->insns may have been changed */
insns = meth->insns + insnIdx;
/* continue on as if we just handled a throw-verification-error */
failure = VERIFY_ERROR_NONE;
nextFlags = kInstrCanThrow;
}
}
}
OP_NEW_INSTANCE
会调用dvmOptResolveClass->dvmCheckClassAccess
检查引用类的访问权限。
bool dvmCheckClassAccess(const ClassObject* accessFrom,
const ClassObject* clazz)
{
if (dvmIsPublicClass(clazz))
return true;
return dvmInSamePackage(accessFrom, clazz);
}
/*
* Returns "true" if the two classes are in the same runtime package.
*/
bool dvmInSamePackage(const ClassObject* class1, const ClassObject* class2)
{
/* quick test for intra-class access */
if (class1 == class2)
return true;
/* class loaders must match */
if (class1->classLoader != class2->classLoader)
return false;
/*
* Switch array classes to their element types. Arrays receive the
* class loader of the underlying element type. The point of doing
* this is to get the un-decorated class name, without all the
* "[[L...;" stuff.
*/
if (dvmIsArrayClass(class1))
class1 = class1->elementClass;
if (dvmIsArrayClass(class2))
class2 = class2->elementClass;
/* check again */
if (class1 == class2)
return true;
/*
* We have two classes with different names. Compare them and see
* if they match up through the final '/'.
*
* Ljava/lang/Object; + Ljava/lang/Class; --> true
* LFoo; + LBar; --> true
* Ljava/lang/Object; + Ljava/io/File; --> false
* Ljava/lang/Object; + Ljava/lang/reflect/Method; --> false
*/
int commonLen;
commonLen = strcmpCount(class1->descriptor, class2->descriptor);
if (strchr(class1->descriptor + commonLen, '/') != NULL ||
strchr(class2->descriptor + commonLen, '/') != NULL)
{
return false;
}
return true;
}
dvmCheckClassAccess检查时如果类是public直接返回true,否则还会验证classloader是否一致
。
我们再看OP_INVOKE_VIRTUAL
等指令,会调用verifyInvocationArgs->dvmOptResolveMethod->dvmCheckMethodAccess
。
Method* dvmOptResolveMethod(ClassObject* referrer, u4 methodIdx,
MethodType methodType, VerifyError* pFailure)
{
DvmDex* pDvmDex = referrer->pDvmDex;
Method* resMethod;
assert(methodType == METHOD_DIRECT ||
methodType == METHOD_VIRTUAL ||
methodType == METHOD_STATIC);
LOGVV("--- resolving method %u (referrer=%s)", methodIdx,
referrer->descriptor);
resMethod = dvmDexGetResolvedMethod(pDvmDex, methodIdx);
if (resMethod == NULL) {
const DexMethodId* pMethodId;
ClassObject* resClass;
pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx);
resClass = dvmOptResolveClass(referrer, pMethodId->classIdx, pFailure);
...
/* access allowed? */
tweakLoader(referrer, resMethod->clazz);
//检查方法的访问权限
bool allowed = dvmCheckMethodAccess(referrer, resMethod);
untweakLoader(referrer, resMethod->clazz);
if (!allowed) {
IF_ALOGI() {
char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
ALOGI("DexOpt: illegal method access (call %s.%s %s from %s)",
resMethod->clazz->descriptor, resMethod->name, desc,
referrer->descriptor);
free(desc);
}
if (pFailure != NULL)
*pFailure = VERIFY_ERROR_ACCESS_METHOD;
return NULL;
}
return resMethod;
}
dvmCheckMethodAccess
校验逻辑:
bool dvmCheckMethodAccess(const ClassObject* accessFrom, const Method* method)
{
return checkAccess(accessFrom, method->clazz, method->accessFlags);
}
/*
* Validate method/field access.
*/
static bool checkAccess(const ClassObject* accessFrom,
const ClassObject* accessTo, u4 accessFlags)
{
/* quick accept for public access */
if (accessFlags & ACC_PUBLIC)
return true;
/* quick accept for access from same class */
if (accessFrom == accessTo)
return true;
/* quick reject for private access from another class */
if (accessFlags & ACC_PRIVATE)
return false;
/*
* Semi-quick test for protected access from a sub-class, which may or
* may not be in the same package.
*/
if (accessFlags & ACC_PROTECTED)
if (dvmIsSubClass(accessFrom, accessTo))
return true;
/*
* Allow protected and private access from other classes in the same
* package.
*/
return dvmInSamePackage(accessFrom, accessTo);
}
如果该方法是public,checkAccess直接放回true,如果是protected,检查调用方法所在类和当前类是否是父子关系,如果不是调用dvmInSamePackage
这个前面已经分析过。
热部署方案影响
最后,我们得出结论:如果补丁类引用了非public类(补丁类用独立的classloader),verifyInstruction的执行结果是*pFailure = VERIFY_ERROR_ACCESS_METHOD;
,之后会执行replaceFailingInstruction->dvmUpdateCodeUnit
更新潜在的错误码,将指令替换成了OP_THROW_VERIFICATION_ERROR
,同时verifyInstruction
返回为true,所以补丁加载的时候是正常的,收不到任何异常。
但是调用的时候,OP_THROW_VERIFICATION_ERROR
指令会直接抛出异常。