共用SystemUid ABI匹配错误

1.问题描述:

在共用SystemUid应用安装后出现下面报错:

java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/packagename-2/lib/arm/libxxxxxx.so" is 32-bit instead of 64-bit
 
 
  • 1

问题出现后检查libaudiooperate.so的确是32位的so,然后查看了下应用放置这个so的位置如下
这里写图片描述
也就是说并没有放错文件位置。其实通过报错的路径来看也可以知道so文件应该是没有放错位置的。于是重新dump应用的包信息,发现对应信息如下:

 Package [packagename] (ba662b2):
   userId=1000
   sharedUser=SharedUserSetting{db1f365 android.uid.system/1000}
   pkg=Package{ed1d24c packagename}
   codePath=/data/app/packagename-1
   resourcePath=/data/app/packagename-1
   legacyNativeLibraryDir=/data/app/packagename-1/lib
   primaryCpuAbi=arm64-v8a
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

很好基本可以知道问题所在了,就是这个primaryCpuAbi值出现了错误,到底是为什么会出现了这个问题呢,需要了解下系统是如何确定这个值的。以下内容大部分来源于 Android中app进程ABI确定过程 部分内容根据我们系统代码差异进行了调整


2. abi相关property

我们先来看几个和abi相关的系统property(以我自己的系统为例):

[ro.product.cpu.abi]: [arm64-v8a]
[ro.product.cpu.abilist]: [arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [arm64-v8a]
 
 
  • 1
  • 2
  • 3
  • 4

ro.product.cpu.abilist的值表明当前系统所支持所有的ABI类型ro.product.cpu.abilist32和ro.product.cpu.abilist64分别表示系统所支持的32位和64位的ABI类型。需要注意的是,这些property的排序代表着ABI的优先级,比如ro.product.cpu.abilist的值里arm64-v8a排在第一个,就表明如果没有指定,arm64-v8a就会成为app进程默认启动的关联ABI。


3. primaryCpuAbi值的确定

上面提到过app.info.primaryCpuAbi的值会对app进程最终的运行架构产生影响,那app.info.primaryCpuAbi的值又是在哪里确定的呢,答案就在PKMS(PackageManagerService)里。 在PKMS的scanPackageDirtyLI 方法里会对app.info.primaryCpuAbi的值产生影响。


3.1 scanPackageDirtyLI

先看看scanPackageDirtyLI方法里和primaryCpuAbi相关的代码:

scanPackageDirtyLI() {
        ... ...
        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
                // 这个方法里会通过apk包里包含的so库的架构来决定app的primaryCpuAbi的值
            derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);

            // Some system apps still use directory structure for native libraries
            // in which case we might end up not detecting abi solely based on apk
            // structure. Try to detect abi based on directory structure.
            if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
                    pkg.applicationInfo.primaryCpuAbi == null) {
                // 如果是system app,并且这个app没有通过上面的函数找到primaryCpuAbi的值
                setBundledAppAbisAndRoots(pkg, pkgSetting);
                // setNativeLibraryPaths方法会根据CpuAbi的值确定apk使用的so库的安装路径
                setNativeLibraryPaths(pkg);
            }
        } else {
            if ((scanFlags & SCAN_MOVE) != 0) {
                // We haven't run dex-opt for this move (since we've moved the compiled output too)
                // but we already have this packages package info in the PackageSetting. We just
                // use that and derive the native library path based on the new codepath.
                pkg.applicationInfo.primaryCpuAbi = pkgSetting.primaryCpuAbiString;
                pkg.applicationInfo.secondaryCpuAbi = pkgSetting.secondaryCpuAbiString;
            }

            // Set native library paths again. For moves, the path will be updated based on the
            // ABIs we've determined above. For non-moves, the path will be updated based on the
            // ABIs we determined during compilation, but the path will depend on the final
            // package path (after the rename away from the stage path).
            setNativeLibraryPaths(pkg);
        }

        // 当前解析的apk是framework-res.apk, 对这个特殊的apk, 让它的ABI值的系统相同, 在我这里,它就是arm64-v8a
        // This is a special case for the "system" package, where the ABI is
        // dictated by the zygote configuration (and init.rc). We should keep track
        // of this ABI so that we can deal with "normal" applications that run under
        // the same UID correctly.
        if (mPlatformPackage == pkg) {
            pkg.applicationInfo.primaryCpuAbi = VMRuntime.getRuntime().is64Bit() ?
                    Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
        }

        ... ...

        if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {
            // We don't do this here during boot because we can do it all
            // at once after scanning all existing packages.
            //
            // We also do this *before* we perform dexopt on this package, so that
            // we can avoid redundant dexopts, and also to make sure we've got the
            // code and package path correct.
            adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
                        pkg, true /* boot complete */);
        }

    }
 
 
  • 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

从上面的这段代码可以看到:

对所有的app,会先通过derivePackageAbi()方法尝试确定app的primaryCpuAbi的值如果是system app,并且通过derivePackageAbi()方法没有确定primaryCpuAbi的值,会再尝试通过setBundledAppAbisAndRoots()方法来确定, 需要注意的是,无论是第三方app还是系统app, 运行完这段代码之后,仍然存在primaryCpuAbi值为空的情况,这是正常现象

接着先来看下derivePackageAbi()方法是如何确定primaryCpuAbi的值的:

public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
                                 String cpuAbiOverride, boolean extractLibs) {
        // 这里会先设置一个默认的so库安装路径
        setNativeLibraryPaths(pkg);

        if (isMultiArch(pkg.applicationInfo)) {
            // 这里处理的是支持两种abi的apk, 这种apk的AndroidManifest.xml里会设置android:multiarch为true
            ... ...
        } else {
            String[] abiList = (cpuAbiOverride != null) ?
                    new String[]{cpuAbiOverride} : Build.SUPPORTED_ABIS;


            final int copyRet;
            // 这是一个JNI函数,作用就是根据apk包里的lib/目录下的.so的ABI确定返回值
            copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                    nativeLibraryRoot, abiList, useIsaSpecificSubdirs);

            // 根据copyRet的值,确定当前app的primaryCpuAbi值
            if (copyRet >= 0) {
                pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
            } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
                pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
            } else if (needsRenderScriptOverride) {
                pkg.applicationInfo.primaryCpuAbi = abiList[0];
            }

        }
        // 到这里有一些app已经确定了primaryCpuAbi的值,所以再调一次这个函数,更新它使用的.so库的安装位置
        setNativeLibraryPaths(pkg);
    }
 
 
  • 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

通过这段代码会可以看出:

一些apk包里lib目录下有.so文件的,可以通过.so文件的ABI来确定app的primaryCpuAbi的值对于那些lib下没有.so文件的apk, 比如不使用so库的或者是系统app,运行完这个方法之后,primaryCpuAbi的值仍然是空 。

接下来看下系统app是如何通过setBundledAppAbisAndRoots()方法来确定primaryCpuAbi的值的:

   private static void setBundledAppAbi(PackageParser.Package pkg, String apkRoot, String apkName) {
        final File codeFile = new File(pkg.codePath);

        final boolean has64BitLibs;
        final boolean has32BitLibs;
        if (isApkFile(codeFile)) {
            // 只有framework-res.apk这个包会进这个if分支,has64BitLibs和has32BitLibs的值都是false
            // 在前面scanPackageDirtyLI里有说过,这个app的primaryCpuAbi的值是arm64-v8a
            has64BitLibs = (new File(apkRoot, new File(LIB64_DIR_NAME, apkName).getPath())).exists();
            has32BitLibs = (new File(apkRoot, new File(LIB_DIR_NAME, apkName).getPath())).exists();
        } else {
            // 对于其它的app, codeFile是apk所在的路径
            final File rootDir = new File(codeFile, LIB_DIR_NAME);

            final String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_64_BIT_ABIS[0]);
            // 通过判断/system/app/${APP_NAME}/lib64这个文件夹是否存在决定has64BitLibs的值
            has64BitLibs = (new File(rootDir, isa)).exists();

            final String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_32_BIT_ABIS[0]);
            // 通过判断/system/app/${APP_NAME}/lib这个文件夹是否存在决定has32BitLibs的值
            has32BitLibs = (new File(rootDir, isa)).exists();

        }

        // 下面这一段会根据has64BitLibs和has32BitLibs的值来确定app的primaryCpuAbi的值
        if (has64BitLibs && !has32BitLibs) {
            pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
            pkg.applicationInfo.secondaryCpuAbi = null;
        } else if (has32BitLibs && !has64BitLibs) {
            pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
            pkg.applicationInfo.secondaryCpuAbi = null;
        } else if (has32BitLibs && has64BitLibs) {
            if (VMRuntime.is64BitInstructionSet(getPreferredInstructionSet())) {
                pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
                pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
            } else {
                pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
                pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
            }
        } else {
            pkg.applicationInfo.primaryCpuAbi = null;
            pkg.applicationInfo.secondaryCpuAbi = null;
        }
    }
 
 
  • 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

根据上面的代码,可以知道:

对系统app而言,根据/system/app/{appNAME}/lib和/system/app/{appNAME}/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值当然,如果系统app不存在上述两个文件夹,那它的primaryCpuAbi的值仍然为空

所以在经过上述操作之后,会存在以下四种情况:

无论是系统app还是第三方app, 如果apk包里lib目录存在.so文件,会根据.so文件来确定primaryCpuAbi的值如果是系统app, apk包里又不存在.so文件,就会进一步根据/system/app/{appNAME}/lib/lib和/system/app/{appNAME}/lib/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值对于framework-res.apk为个特殊的apk文件,它的primaryCpuAbi的值由虚拟机是什么架构来决定,在我这里,它是arm64-v8a对于其余的apk, 它们的primaryCpuAbi的值仍然为空


3.2 adjustCpuAbisForSharedUserLPw

下面看下adjustCpuAbisForSharedUserLPw代码

    private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
            PackageParser.Package scannedPackage, boolean bootComplete) {
        String requiredInstructionSet = null;
        if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
            requiredInstructionSet = VMRuntime.getInstructionSet(
                     scannedPackage.applicationInfo.primaryCpuAbi);
        }

        PackageSetting requirer = null;
        for (PackageSetting ps : packagesForUser) {
            // If packagesForUser contains scannedPackage, we skip it. This will happen
            // when scannedPackage is an update of an existing package. Without this check,
            // we will never be able to change the ABI of any package belonging to a shared
            // user, even if it's compatible with other packages.
            if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
                if (ps.primaryCpuAbiString == null) {
                    continue;
                }

                final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString);
                if (requiredInstructionSet != null && !instructionSet.equals(requiredInstructionSet)) {
                    // We have a mismatch between instruction sets (say arm vs arm64) warn about
                    // this but there's not much we can do.
                    String errorMessage = "Instruction set mismatch, "
                            + ((requirer == null) ? "[caller]" : requirer)
                            + " requires " + requiredInstructionSet + " whereas " + ps
                            + " requires " + instructionSet;
                    Slog.w(TAG, errorMessage);
                }
                /// M: Use instructionSet of "android" package for android.uid.system.
                //此处MTK强行将systemUid的应用abi设置成跟framework-res.apk 的一致了
                if (requiredInstructionSet == null || ps.name.equals("android")) {
                    requiredInstructionSet = instructionSet;
                    requirer = ps;
                }
            }
        }

        if (requiredInstructionSet != null) {
            String adjustedAbi;
            if (requirer != null) {
                // requirer != null implies that either scannedPackage was null or that scannedPackage
                // did not require an ABI, in which case we have to adjust scannedPackage to match
                // the ABI of the set (which is the same as requirer's ABI)
                adjustedAbi = requirer.primaryCpuAbiString;
                if (scannedPackage != null) {
                    scannedPackage.applicationInfo.primaryCpuAbi = adjustedAbi;
                }
            } else {
                // requirer == null implies that we're updating all ABIs in the set to
                // match scannedPackage.
                adjustedAbi =  scannedPackage.applicationInfo.primaryCpuAbi;
            }

            for (PackageSetting ps : packagesForUser) {
                if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
                    if (ps.primaryCpuAbiString != null) {
                        continue;
                    }

                    ps.primaryCpuAbiString = adjustedAbi;
                    if (ps.pkg != null && ps.pkg.applicationInfo != null &&
                            !TextUtils.equals(adjustedAbi, ps.pkg.applicationInfo.primaryCpuAbi)) {
                        ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
                        Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi
                                + " (requirer="
                                + (requirer == null ? "null" : requirer.pkg.packageName)
                                + ", scannedPackage="
                                + (scannedPackage != null ? scannedPackage.packageName : "null")
                                + ")");
                        try {
                            if (!isApkFile(new File(ps.codePathString))) {
                                mInstaller.rmdex(ps.codePathString,
                                        getDexCodeInstructionSet(getPreferredInstructionSet()));
                            }
                        } catch (InstallerException ignored) {
                        }
                    }
                }
            }
        }
    }   

这段代码的作用就是调整使用相同UID的package的primaryCpuAbi的值,将那些还没有确定primaryCpuAbi的package用已经确定了的Abi的值代替。


4. 总结

最后来总结一下Android系统确定app进程关联哪种ABI的流程:

如果apk包中lib文件夹下有.so库,就根据这个.so库的架构模式,确定app的primaryCpuAbi的值对于system app, 如果没法通过第一步确定primaryCpuAbi的值,PKMS会根据/system/app/{appNAME}/lib和/system/app/{appNAME}/lib64这两个文件夹是否存在,来确定它的primaryCpuAbi的值对于还没有确定的app, 在最后还会将自己的primaryCpuAbi值与和他使用相同UID的package的值设成一样对于到这里还没有确认primaryCpuAbi的app(此处MTK做了处理,共用system uid的应用会被强行设置成跟framework-res.apk 一致的primaryCpuAbi,也就是让它的ABI值的系统相同 ),就会在启动进程时使用ro.product.cpu.abilist 这个property的值的第一项作为它关联的ABI。分析到这基本可以认为应该是MTK添加代码出现的问题,在原生代码的逻辑下是不会出现篡改的问题的,但是MTK希望system uid的未确定primaryCpuAbi的应用使用系统的ABI值,导致了异常

异常应用由于共用system uid所以最后他的primaryCpuAbi被篡改为arm64这就导致了异常,关键log如下

Instruction set mismatch, [caller] requires arm whereas PackageSetting{2abf67 packagename1/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{246b775 packagename2/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{3de6c4e packagename3/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{44348b9 packagename4/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{4d855f1 packagename5/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{548ca81 packagename6/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{6536eac packagename7/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{8e484fe packagename8/1000} requires arm64
Instruction set mismatch, [caller] requires arm whereas PackageSetting{901fb49 android/1000} requires arm64
Instruction set mismatch, PackageSetting{901fb49 android/1000} requires arm64 whereas PackageSetting{c99eb03 packagename9/1000} requires arm
Instruction set mismatch, PackageSetting{901fb49 android/1000} requires arm64 whereas PackageSetting{db8ff5a packagename10/1000} requires arm
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值