热修复Java探索之Dalvik生成odex时类校验的权限检查

一个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);

所以我们找到了关键函数verifyAndOptimizeClassverifyAndOptimizeClass首先校验类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指令会直接抛出异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值