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