Android ABI issue analysis

什么是ABI

ABI 全称 application binary interface,是一个机器语言级别的接口,描述的是二进制代码之间的兼容关系,这也意味着一起工作的二进制组件是ABI兼容的。一个SO库想要调用另一个SO库的函数,就要求它们的ABI兼容。Stack overflow上有一个以API为类比来说明什么是ABI的回答What is Application Binary Interface

Process的位数下图是app启动的大致流程,图片来自http://gityuan.com/2016/03/26/app-process-create/

1. 创建出来的进程究竟是32位还是64位呢?

/**
     * Start a new process.
     * 
     * <p>If processes are enabled, a new process is created and the
     * static main() function of a <var>processClass</var> is executed there.
     * The process will continue running after this function returns.
     * 
     * <p>If processes are not enabled, a new thread in the caller's
     * process is created and main() of <var>processClass</var> called there.
     * 
     * <p>The niceName parameter, if not an empty string, is a custom name to
     * give to the process instead of using processClass.  This allows you to
     * make easily identifyable processes even if you are using the same base
     * <var>processClass</var> to start them.
     * 
     * @param processClass The class to use as the process's main entry
     *                     point.
     * @param niceName A more readable name to use for the process.
     * @param uid The user-id under which the process will run.
     * @param gid The group-id under which the process will run.
     * @param gids Additional group-ids associated with the process.
     * @param debugFlags Additional flags.
     * @param targetSdkVersion The target SDK version for the app.
     * @param seInfo null-ok SELinux information for the new process.
     * @param abi non-null the ABI this app should be started with.
     * @param instructionSet null-ok the instruction set to use.
     * @param appDataDir null-ok the data directory of the app.
     * @param zygoteArgs Additional arguments to supply to the zygote process.
     * 
     * @return An object that describes the result of the attempt to start the process.
     * @throws RuntimeException on fatal start failure
     * 
     * {@hide}
     */
    public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] zygoteArgs) {

函数start必须要有一个参数String abi。这个参数决定了启动的进程是64位还是32位。

2、这个参数从哪里来呢?

函数start的唯一调用者就是startProcessLocked, 下面看看这个函数的删减版。

private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
            ...

            String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
            if (requiredAbi == null) {
                requiredAbi = Build.SUPPORTED_ABIS[0];
            }

            String instructionSet = null;
            if (app.info.primaryCpuAbi != null) {
                instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
            }

            app.gids = gids;
            app.requiredAbi = requiredAbi;
            app.instructionSet = instructionSet;

            // Start the process.  It will either succeed and return a result containing
            // the PID of the new process, or else throw a RuntimeException.
            boolean isActivityProcess = (entryPoint == null);
            if (entryPoint == null) entryPoint = "android.app.ActivityThread";
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
                    app.processName);
            checkTime(startTime, "startProcess: asking zygote to start proc");
            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);
           ...
    }

正常运行的时候,代码中的abiOverride几乎总是为空,也就是说start函数的abi参数由app.info.primaryCpuAbi决定。如果app.info.primaryCpuAbi为空,就用Build.SUPPORTED_ABIS[0],也就是ro.product.cpu.abilist中,第一个值。

3、app.info.primaryCpuAbi是哪里来的呢?

系统运行起来之后,会将packages的信息保存在/data/system/packages.xml文件中,所以查看该文件可用知道对应App的ABI,在遇到问题时,可用查看该文件,但是也不能回答app.info.primaryCpuAbi是哪里来的。

这个值是在解析apk的时候确定下来的,如果确定不了,app.info.primaryCpuAbi就为空。

不论是安装应用,还是开机,scanPackageLI总是要被执行的一个函数。从Android L开始,引入了新函数scanPackageDirtyLI 。本文关注的ABI就是在scanPackageDirtyLI中被判断出来的。

PackageManagerService构造函数中会对系统里所有的App执行scanPackageDirtyLI

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user)

在进入scanPackageDirtyLI的时候,pkg.applicationInfo.primaryCpuAbi值为空。因为pkg才实例化不久,primaryCpuAbi值还没有初始化。

函数scanPackageDirtyLI很长,经过各种判断,一些pkg.applicationInfo.primaryCpuAbi被确定下来。比如有so的App就根据so确定下来了。

对于高通项目,要注意derivePackageAbi函数。这个函数会调到一个native方法findSupportedAbi。这个方法在文件com_android_internal_content_NativeLibraryHelper.cpp中,被高通修改过。

     if(status <= 0) {
        // Scan the 'assets' folder only if
        // the abi (after scanning the lib folder)
        // is not already set to 32-bit (i.e '1' or '2').

        int asset_status = NO_NATIVE_LIBRARIES;
        int rc = initAssetsVerifierLib();

        if (rc == LIB_INITED_AND_SUCCESS) {
            asset_status = GetAssetsStatusFunc(zipFile, supportedAbis, numAbis);
        } else {
            ALOGE("Failed to load assets verifier: %d", rc);
        }
        if(asset_status >= 0) {
            // Override the ABI only if
            // 'asset_status' is a valid ABI (64-bit or 32-bit).
            // This is to prevent cases where 'lib' folder
            // has native libraries, but
            // 'assets' folder has none.
            status = asset_status;
        }

意思就是如果apk的assets文件夹下有so库,系统会根据so库的ABI去决定app的ABI。

还有一个函数需要注意adjustCpuAbisForSharedUserLPw

 /**
     * Adjusts ABIs for a set of packages belonging to a shared user so that they all match.
     * i.e, so that all packages can be run inside a single process if required.
     *
     * Optionally, callers can pass in a parsed package via {@code newPackage} in which case
     * this function will either try and make the ABI for all packages in {@code packagesForUser}
     * match {@code scannedPackage} or will update the ABI of {@code scannedPackage} to match
     * the ABI selected for {@code packagesForUser}. This variant is used when installing or
     * updating a package that belongs to a shared user.
     *
     * NOTE: We currently only match for the primary CPU abi string. Matching the secondary
     * adds unnecessary complexity.
     */
    private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
            PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt,
            boolean bootComplete) {

这个函数其实是把有相同android:sharedUserId的packages分成一个组,把组里找到的第一个已经确定ABI的package的ABI作为组里其它还没确定ABI的packages的ABI。

PackageManagerService构造函数执行完毕之后,系统中绝大部分packages的ABI都确定了,还有极少数没有确定的,相关信息都写入文件/data/system/packages.xml

问题描述

手机重启之后会有应用反复的crash,导致系统不能使用。
如果再次重启,问题可能就消失了。但是再次重启,可能又出现了。
现象看上去很诡异!

问题分析

crash的问题,一般都会有明显的log。

03-01 00:02:40.792 4133 4133 E AndroidRuntime: FATAL EXCEPTION: main
03-01 00:02:40.792 4133 4133 E AndroidRuntime: Process: com.quicinc.cne.CNEService, PID: 4133
03-01 00:02:40.792 4133 4133 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate application com.quicinc.cne.CNEService.CNEServiceApp: java.lang.ClassNotFoundException: Didn't find class "com.quicinc.cne.CNEService.CNEServiceApp" on path: DexPathList[[zip file "/system/framework/com.quicinc.cne.jar", zip file "/system/priv-app/CNEService/CNEService.apk"],nativeLibraryDirectories=[/system/priv-app/CNEService/lib/arm64, /system/priv-app/CNEService/CNEService.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]
03-01 00:02:40.792 4133 4133 E AndroidRuntime: at android.app.LoadedApk.makeApplication(LoadedApk.java:578)
...

从log看,在apk中找不到class文件。

CNEService是一个系统内置应用,编译的是64位系统,user版本,有做odex,apk中的dex文件被删除。

03-01 00:02:36.367 1284 1284 I PackageManager: Adjusting ABI for : com.quicinc.cne.CNEService to armeabi-v7a

只有arm64文件夹且被认为是armeabi-v7a,系统找不到odex,到apk中去找,但是dex已经删除了,故进程崩溃。

正常情况下,CNEService 的primaryCpuAbi应该arm64-v8a,为什么这里有时候就变成了armeabi-v7a?

上面一节已经描述过primaryCpuAbi的来源了。
从log中可以知道CNEService 的ABI在函数adjustCpuAbisForSharedUserLPw中被确定下来的。
还发现有如下log:

03-01 00:02:36.364 1284 1284 W PackageManager: Instruction set mismatch, PackageSetting{43d890 com.qti.smq.qualcommFeedback/1000} requires arm whereas PackageSetting{e6e3c66 com.caf.fmradio/1000} requires arm64

在函数adjustCpuAbisForSharedUserLPw被执行之前,一些package的ABI已经确定出来了。比如log中提到的qualcommFeedback和fmradio。

也就是说qualcommFeedback,fmradio,CNEService android:sharedUserId是一样的,且在执行adjustCpuAbisForSharedUserLPw之前qualcommFeedback的ABI已经确定,是armeabi-v7a,而fmradio的ABI是arm64-v8a。但是因为qualcommFeedback是第一个被找到,故认为其它的没有确定ABI的packages的ABI和qualcommFeedback一样,即CNEService的ABI是armeabi-v7a。如果fmradio先被找到,就会认为其它的没有确定ABI的packages的ABI和fmradio一样,即CNEService的ABI是arm64-v8a。

至此,能解释上面诡异现象了。

  • 当CNEService的ABI是arm64-v8a时,由于存在 arm64/CNEService.odex ,程序正常运行。

  • 当CNEService的ABI是armeabi-v7a时,不存在 arm/CNEService.odex ,CNEService.apk 没有dex,程序崩溃。

修改方法有多种:

  1. 同时生成arm/CNEService.odex 和arm64/CNEService.odex
  2. 保留apk中的dex文件
  3. 修改android:sharedUserId
  4. 把和CNEService的 android:sharedUserId一样的app都变成arm64-v8a。
    ......

通过对多份log的分析发现,qualcommFeedback的ABI始终是armeabi-v7a。

之前有提到一个native方法findSupportedAbi,这个函数经过高通修改之后会根据assets文件夹下的so决定得ABI。
打开APK之后,确实在assets下发现了一个32位的so库。

再没了所有疑惑之后,解决问题就不写了。

REF

https://read01.com/BDyndB.html
https://my.oschina.net/bugly/blog/738360
https://my.oschina.net/liucundong/blog/653128
http://gityuan.com/2016/03/26/app-process-create/
https://en.wikipedia.org/wiki/Application_binary_interface
https://mssun.me/blog/android-art-runtime-2-dex2oat.html
http://www.iloveandroid.net/2016/06/27/Android_PackageManagerService-9/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容