Android ABI issue analysis

什么是ABI

ABI 全稱 application binary interface,是一個機器語言級別的接口,描述的是二進制代碼之間的兼容關(guān)系,這也意味著一起工作的二進制組件是ABI兼容的。一個SO庫想要調(diào)用另一個SO庫的函數(shù),就要求它們的ABI兼容。Stack overflow上有一個以API為類比來說明什么是ABI的回答What is Application Binary Interface。

Process的位數(shù)下圖是app啟動的大致流程,圖片來自http://gityuan.com/2016/03/26/app-process-create/。

1. 創(chuàng)建出來的進程究竟是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) {

函數(shù)start必須要有一個參數(shù)String abi。這個參數(shù)決定了啟動的進程是64位還是32位。

2、這個參數(shù)從哪里來呢?

函數(shù)start的唯一調(diào)用者就是startProcessLocked, 下面看看這個函數(shù)的刪減版。

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函數(shù)的abi參數(shù)由app.info.primaryCpuAbi決定。如果app.info.primaryCpuAbi為空,就用Build.SUPPORTED_ABIS[0],也就是ro.product.cpu.abilist中,第一個值。

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

系統(tǒng)運行起來之后,會將packages的信息保存在/data/system/packages.xml文件中,所以查看該文件可用知道對應(yīng)App的ABI,在遇到問題時,可用查看該文件,但是也不能回答app.info.primaryCpuAbi是哪里來的。

這個值是在解析apk的時候確定下來的,如果確定不了,app.info.primaryCpuAbi就為空。

不論是安裝應(yīng)用,還是開機,scanPackageLI總是要被執(zhí)行的一個函數(shù)。從Android L開始,引入了新函數(shù)scanPackageDirtyLI 。本文關(guān)注的ABI就是在scanPackageDirtyLI中被判斷出來的。

PackageManagerService構(gòu)造函數(shù)中會對系統(tǒng)里所有的App執(zhí)行scanPackageDirtyLI

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

在進入scanPackageDirtyLI的時候,pkg.applicationInfo.primaryCpuAbi值為空。因為pkg才實例化不久,primaryCpuAbi值還沒有初始化。

函數(shù)scanPackageDirtyLI很長,經(jīng)過各種判斷,一些pkg.applicationInfo.primaryCpuAbi被確定下來。比如有so的App就根據(jù)so確定下來了。

對于高通項目,要注意derivePackageAbi函數(shù)。這個函數(shù)會調(diào)到一個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庫,系統(tǒng)會根據(jù)so庫的ABI去決定app的ABI。

還有一個函數(shù)需要注意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) {

這個函數(shù)其實是把有相同android:sharedUserId的packages分成一個組,把組里找到的第一個已經(jīng)確定ABI的package的ABI作為組里其它還沒確定ABI的packages的ABI。

PackageManagerService構(gòu)造函數(shù)執(zhí)行完畢之后,系統(tǒng)中絕大部分packages的ABI都確定了,還有極少數(shù)沒有確定的,相關(guān)信息都寫入文件/data/system/packages.xml

問題描述

手機重啟之后會有應(yīng)用反復(fù)的crash,導(dǎo)致系統(tǒng)不能使用。
如果再次重啟,問題可能就消失了。但是再次重啟,可能又出現(xiàn)了。
現(xiàn)象看上去很詭異!

問題分析

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是一個系統(tǒng)內(nèi)置應(yīng)用,編譯的是64位系統(tǒng),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文件夾且被認(rèn)為是armeabi-v7a,系統(tǒng)找不到odex,到apk中去找,但是dex已經(jīng)刪除了,故進程崩潰。

正常情況下,CNEService 的primaryCpuAbi應(yīng)該arm64-v8a,為什么這里有時候就變成了armeabi-v7a?

上面一節(jié)已經(jīng)描述過primaryCpuAbi的來源了。
從log中可以知道CNEService 的ABI在函數(shù)adjustCpuAbisForSharedUserLPw中被確定下來的。
還發(fā)現(xiàn)有如下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

在函數(shù)adjustCpuAbisForSharedUserLPw被執(zhí)行之前,一些package的ABI已經(jīng)確定出來了。比如log中提到的qualcommFeedback和fmradio。

也就是說qualcommFeedback,fmradio,CNEService android:sharedUserId是一樣的,且在執(zhí)行adjustCpuAbisForSharedUserLPw之前qualcommFeedback的ABI已經(jīng)確定,是armeabi-v7a,而fmradio的ABI是arm64-v8a。但是因為qualcommFeedback是第一個被找到,故認(rèn)為其它的沒有確定ABI的packages的ABI和qualcommFeedback一樣,即CNEService的ABI是armeabi-v7a。如果fmradio先被找到,就會認(rèn)為其它的沒有確定ABI的packages的ABI和fmradio一樣,即CNEService的ABI是arm64-v8a。

至此,能解釋上面詭異現(xiàn)象了。

  • 當(dāng)CNEService的ABI是arm64-v8a時,由于存在 arm64/CNEService.odex ,程序正常運行。

  • 當(dāng)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的分析發(fā)現(xiàn),qualcommFeedback的ABI始終是armeabi-v7a。

之前有提到一個native方法findSupportedAbi,這個函數(shù)經(jīng)過高通修改之后會根據(jù)assets文件夾下的so決定得ABI。
打開APK之后,確實在assets下發(fā)現(xiàn)了一個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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容