Android——APK 在32bit/64bit平台 動態庫問題

來源:互聯網
上載者:User

Android——APK 在32bit/64bit平台 動態庫問題

目前64bit android系統也慢慢的多了,看到也有apk聲稱支援64bit system,然後就往裡面打包搞了個arm64-v8a 目錄,放了個64bit的so,但是apk代碼裡面卻不按規範去load so ,導致一系列 file not found 異常~

 

apk lib目錄:

先看下apk中的lib打包的目錄:

依次代表不同類型的cpu<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="pms安裝路徑">PMS安裝路徑:

pms install 流程比較繁雜,只關注so相關的scanPackageDirtyLI函數中:

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {            ...             //invoke installer to do the actual installation  //作為外部apk 建立data目錄相關項目             //rameworksativecmdsinstalldcommands.c  install()中建立                int ret = **createDataDirsLI**(pkgName, pkg.applicationInfo.uid,                                           pkg.applicationInfo.seinfo);            ...    if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {        ...           setBundledAppAbisAndRoots(pkg, pkgSetting);            ...           setNativeLibraryPaths(pkg);      }    else    {    setNativeLibraryPaths(pkg);    ...        if (isAsec) {                        copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);                    } else {                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);                    }    setNativeLibraryPaths(pkg);      if (DEBUG_INSTALL) Slog.i(TAG, Linking native library dir for  + path);            final int[] userIds = sUserManager.getUserIds();            synchronized (mInstallLock) {                // Create a native library symlink only if we have native libraries                // and if the native libraries are 32 bit libraries. We do not provide                // this symlink for 64 bit libraries.                if (pkg.applicationInfo.primaryCpuAbi != null &&                        **!VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)**) {                    final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;                    for (int userId : userIds) {if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0)    {                            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,                                    Failed linking native library dir (user= + userId + ));                        }                    }                }            }    }}

看下system app 的安裝配置函數 setBundledAppAbisAndRoots

    /**     * Calculate the abis and roots for a bundled app. These can uniquely     * be determined from the contents of the system partition, i.e whether     * it contains 64 or 32 bit shared libraries etc. We do not validate any     * of this information, and instead assume that the system was built     * sensibly.     */    private void setBundledAppAbisAndRoots(PackageParser.Package pkg,                                           PackageSetting pkgSetting) {        final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath());        // If /system/lib64/apkname exists, assume that is the per-package        // native library directory to use; otherwise use /system/lib/apkname.        final String apkRoot = calculateBundledApkRoot(pkg.applicationInfo.sourceDir);        setBundledAppAbi(pkg, apkRoot, apkName);        // pkgSetting might be null during rescan following uninstall of updates        // to a bundled app, so accommodate that possibility.  The settings in        // that case will be established later from the parsed package.        //        // If the settings aren't null, sync them up with what we've just derived.        // note that apkRoot isn't stored in the package settings.        if (pkgSetting != null) {            pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi;            pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;        }    }

主要是在setBundledAppAbi中:

    /**     * Deduces the ABI of a bundled app and sets the relevant fields on the     * parsed pkg object.     *     * @param apkRoot the root of the installed apk, something like {@code /system} or {@code /oem}     *        under which system libraries are installed.     * @param apkName the name of the installed package.     */    private static void setBundledAppAbi(PackageParser.Package pkg, String apkRoot, String apkName) {        final File codeFile = new File(pkg.codePath);...        if (has64BitLibs && !has32BitLibs) {            // The package has 64 bit libs, but not 32 bit libs. Its primary            // ABI should be 64 bit. We can safely assume here that the bundled            // native libraries correspond to the most preferred ABI in the list.            pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];            pkg.applicationInfo.secondaryCpuAbi = null;        } else if (has32BitLibs && !has64BitLibs) {            // The package has 32 bit libs but not 64 bit libs. Its primary            // ABI should be 32 bit.            pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];            pkg.applicationInfo.secondaryCpuAbi = null;        } else if (has32BitLibs && has64BitLibs) {            // The application has both 64 and 32 bit bundled libraries. We check            // here that the app declares multiArch support, and warn if it doesn't.            //            // We will be lenient here and record both ABIs. The primary will be the            // ABI that's higher on the list, i.e, a device that's configured to prefer            // 64 bit apps will see a 64 bit primary ABI,            if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) == 0) {                Slog.e(TAG, Package:  + pkg +  has multiple bundled libs, but is not multiarch.);            }            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;        } }

根據file 尋找 確定primaryCpuAbi secondaryCpuAbi 變數值,這個也就決定了 這個 apk 由64bit 還是32bit 的zygote去fork 還有nativelibrary 尋找的path

其中nativelibrary的幾個主要函數 setNativeLibraryPaths

/**     * Derive and set the location of native libraries for the given package,     * which varies depending on where and how the package was installed.     */    private void setNativeLibraryPaths(PackageParser.Package pkg) {        final ApplicationInfo info = pkg.applicationInfo;        final String codePath = pkg.codePath;        final File codeFile = new File(codePath);        final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info);        final boolean asecApp = isForwardLocked(info) || isExternal(info);        info.nativeLibraryRootDir = null;        info.nativeLibraryRootRequiresIsa = false;        info.nativeLibraryDir = null;        info.secondaryNativeLibraryDir = null;        if (isApkFile(codeFile)) {            // Monolithic install            if (bundledApp) {                // If /system/lib64/apkname exists, assume that is the per-package                // native library directory to use; otherwise use /system/lib/apkname.                final String apkRoot = calculateBundledApkRoot(info.sourceDir);                final boolean is64Bit = VMRuntime.is64BitInstructionSet(                        getPrimaryInstructionSet(info));                // This is a bundled system app so choose the path based on the ABI.                // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this                // is just the default path.                final String apkName = deriveCodePathName(codePath);                final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;                info.nativeLibraryRootDir = Environment.buildPath(new File(apkRoot), libDir,                        apkName).getAbsolutePath();                if (info.secondaryCpuAbi != null) {                    final String secondaryLibDir = is64Bit ? LIB_DIR_NAME : LIB64_DIR_NAME;                    info.secondaryNativeLibraryDir = Environment.buildPath(new File(apkRoot),                            secondaryLibDir, apkName).getAbsolutePath();                }            } else if (asecApp) {                info.nativeLibraryRootDir = new File(codeFile.getParentFile(), LIB_DIR_NAME)                        .getAbsolutePath();            } else {                final String apkName = deriveCodePathName(codePath);                info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)                        .getAbsolutePath();            }            info.nativeLibraryRootRequiresIsa = false;            info.nativeLibraryDir = info.nativeLibraryRootDir;        } else {            // Cluster install            info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();            info.nativeLibraryRootRequiresIsa = true;            info.nativeLibraryDir = new File(info.nativeLibraryRootDir,                    getPrimaryInstructionSet(info)).getAbsolutePath();            if (info.secondaryCpuAbi != null) {                info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir,                        VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath();            }        }    }

根據pkg application info 來確定nativelibrarydir 依賴info中的 info.primaryCpuAbi

    private static String getPrimaryInstructionSet(ApplicationInfo info) {        if (info.primaryCpuAbi == null) {            return getPreferredInstructionSet();        }        return VMRuntime.getInstructionSet(info.primaryCpuAbi);    }

非system apk 會調用 NativeLibraryHelperfindSupportedAbi 去解析 .apk 檔案,根據系統suportabilist 去尋找 lib目錄下的打包子目錄 找到匹配的abi

向文章開頭的那個 lib目錄 ,在64bit 機器上suportabilist為:

 public static final String[] SUPPORTED_ABIS = getStringList(ro.product.cpu.abilist, ,);
root@:/ # getprop ro.product.cpu.abilist                                 arm64-v8a,armeabi-v7a,armeabi

會匹配arm64-v8a 賦值給 info.primaryCpuAbi

copyNativeBinariesForSupportedAbi 會去copy 前面匹配的lib 目錄到本地

最後設定NativeLibraryPaths ,
如果匹配的是64bit的,也就是arm64-v8a 那麼就不為/data/data/../lib 建立軟連結,這是與32bit 不同的地方

system.loadlibrary

作為動態庫載入的標準介面,直接看實現:

    public static void loadLibrary(String libName) {        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());    }

到Runtime.java中:

/*     * Searches for and loads the given shared library using the given ClassLoader.     */    void loadLibrary(String libraryName, ClassLoader loader) {        if (loader != null) {            String filename = loader.findLibrary(libraryName);            if (filename == null) {                // It's not necessarily true that the ClassLoader used                // System.mapLibraryName, but the default setup does, and it's                // misleading to say we didn't find libMyLibrary.so when we                // actually searched for liblibMyLibrary.so.so.                throw new UnsatisfiedLinkError(loader +  couldn't find  +                                               System.mapLibraryName(libraryName) + );            }            String error = doLoad(filename, loader);            if (error != null) {                throw new UnsatisfiedLinkError(error);            }            return;        }        String filename = System.mapLibraryName(libraryName);        List candidates = new ArrayList();        String lastError = null;        for (String directory : mLibPaths) {            String candidate = directory + filename;            candidates.add(candidate);            if (IoUtils.canOpenReadOnly(candidate)) {                String error = doLoad(candidate, loader);                if (error == null) {                    return; // We successfully loaded the library. Job done.                }                lastError = error;            }        }        if (lastError != null) {            throw new UnsatisfiedLinkError(lastError);        }        throw new UnsatisfiedLinkError(Library  + libraryName +  not found; tried  + candidates);    }

這裡的 ClassLoader loader 實際上會在 apk啟動的時候 初始化好一些相關的 子類 父類 還有參數

大體記錄一下 啟動時 初始流程 :

ActivityThread.java   - handleBindApplication   final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);  LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,                    appContext.getClassLoader(), false, true, false);ContextImpl.java  -getClassLoader()LoadedApk.java    -getClassLoader()     :  mLibDir = aInfo.nativeLibraryDir;mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,                        mBaseClassLoader);ApplicationLoaders.java    -getClassLoader(...) PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent);   //這裡的libPath 就是上面傳下來的aInfo.nativeLibraryDirpublic class PathClassLoader extends BaseDexClassLoaderpublic class BaseDexClassLoader extends ClassLoader

loader.findLibrary(libraryName);

    /**     * Finds the named native code library on any of the library     * directories pointed at by this instance. This will find the     * one in the earliest listed directory, ignoring any that are not     * readable regular files.     *     * @return the complete path to the library or {@code null} if no     * library was found     */    public String findLibrary(String libraryName) {        String fileName = System.mapLibraryName(libraryName);        for (File directory : nativeLibraryDirectories) {            String path = new File(directory, fileName).getPath();            if (IoUtils.canOpenReadOnly(path)) {                return path;            }        }        return null;    }

這裡的nativeLibraryDirectories 即為前面一系列 構造時 設定了值 其中就有 aInfo.nativeLibraryDir

後面的邏輯就不去敘述了, 根據名字在這個目錄下去找 ,然後調用到本地JNI 最終調用 dlopen 載入開啟so,必須是相同位元, 而這個關乎當前進程是屬於64bit 還是 32bit,這個會在zygote fork時區分, 同樣也是由PMS解析時得到的 info.primaryCpuAbi

AMS 請求zygote fork app process選擇

只關心 相關代碼 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;            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);            checkTime(startTime, startProcess: returned from zygote!);...}

Process中真正的socket 請求實現:

return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);

openZygoteSocketIfNeeded 會根據傳下來的abi 去選擇 通訊的socket

而在64bit 機器上,啟動時會 啟動 兩個 zygote service ,用於接收 64 32 的apk 請求:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote    class main    socket zygote stream 660 root system    onrestart write /sys/android_power/request_state wake    onrestart write /sys/power/state on    onrestart restart media    onrestart restart netdservice zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary    class main    socket zygote_secondary stream 660 root system    onrestart restart zygote

可以看到兩個 zygote 進程,基本一致 ,區別在於 64bit 32bit ,註冊socket不同
關於這兩個 zygote 進程啟動時的socket 註冊 就不多說了

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.