apk sharedUserId设为"android.uid.system",安装后出现java.lang.UnsatisfiedLinkError ..is 32-bit instead of 64

在MTK系统中,使用的是64位编译系统,而apk中使用的库编译的是32位的,当将apk的sharedUserId设为"android.uid.system"后,并进行系统签名,安装后打开apk,当调用到按32位编译的库时会出现错误:

              java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/xxx/lib/arm/xxx.so" is 32-bit instead of 64-bit

问题出现是因为xxx.so的确是32位的so,而系统默认使用的是64位的系统,并且so也没有放错文件夹,通过dump包的信息,发现对应信息如下:

        Package [packagename] (ba662b2):

       userId=1000

      sharedUser=SharedUserSetting{db1f365 android.uid.system/1000}

     pkg=Package{ed1d24c xxxx}

     codePath=/data/app/xxxx-1

     resourcePath=/data/app/xxxx-1

     legacyNativeLibraryDir=/data/app/xxxx-1/lib

     primaryCpuAbi=arm64-v8a

从dump出来的信息看是primaryCpuAbi值出现了错误,到底是为什么会出现了这个问题呢,需要了解下系统是如何确定这个值的。

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]

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。

 

在PMS的scanPackageDirtyLI 方法里会对app.info.primaryCpuAbi的值产生影响。

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 */);

        }

从scanPackageDirtyLI可以看到:

对所有的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);

    }

derivePackageAbi中可以看出:

一些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;

     }

 

从setBundledAppAbi,可以知道:

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

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

虚拟机是什么架构来决定在我这里它是arm64-v8a对于其余的apk-它们的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的值仍然为空

接着看adjustCpuAbisForSharedUserLPw:

private void adjustCpuAbisForSharedUserLPw(Set 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.

            //此处(ps.name.equals("android"))MTK强行将systemUid的应用abi设置成跟framework-res.apk 的一致了

            if (requiredInstructionSet == null || ps.name.equals("android")) {

             /* /data/app/下的应用不行将systemUid的应用abi设置成跟framework-res.apk 的一致*/

     +  if (null != requiredInstructionSet && null != scannedPackage
     +      && scannedPackage.codePath.contains("/data/app/")) {
     +      Slog.w(TAG, "data app return " + scannedPackage.codePath);
     +      continue;
     +  }

                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) {

             ...........

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

 

总结一下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值,导致了异常

你可能感兴趣的:(android)