在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:
[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
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值,导致了异常