Multiple ABI and 64bit on Lolipop

Domain/Android 2014.11.08 01:01

 Abi관련 변수들


Build.CPU_ABI

Build.SUPPORTED_ABIS
ro.product.cpu.abilist64
ro.product.cpu.abilist32


=========================

64bit, 32bit library의 경우


BaseDexClassLoader 에서 참조하는 native library는 null인데, 이는 default library path를 사용하는 것이고...

코드를 쫓아가다보면...

'java.library.path"

에서 library directory를 가져오는데(/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java) 이 값은 default로 LD_LIBRARY_PATH 환경변수값을 사용한다.(java_lang_System.cpp)

android_get_LD_LIBRARY_PATH() 함수를 찾아서 가보면, kDefaultLdPaths 에서 값을 읽어오는데, 아래와 같이 정의되어 있다.


91static const char* const kDefaultLdPaths[] = {
92#if defined(__LP64__)
93  "/vendor/lib64",
94  "/system/lib64",
95#else
96  "/vendor/lib",
97  "/system/lib",
98#endif
99  NULL
100};
101

그리고


system library의 경우

system/lib/

system/lib64/

vendor/lib/

vendor/lib64


========================

system library의 경우는 위와 같고... APK에 포함된 library의 경우는... aapt를 확인해야 하는데, 

/frameworks/base/tools/aapt/Command.cpp

를 보면, 

1981            AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
1982            if (dir != NULL) {
1983                if (dir->getFileCount() > 0) {
1984                    SortedVector<String8> architectures;
1985                    for (size_t i=0; i<dir->getFileCount(); i++) {
1986                        architectures.add(ResTable::normalizeForOutput(
1987                                dir->getFileName(i).string()));
1988                    }


코드가 보이는데... 즉 "lib/<archtecture-name>"의 형태로 directory가 구성되었다는 것을 알 수 있다.


====================================

APP이 뜰때, 64bit인지 32bit인지 확인해서 zygote64를 base로 뜰건지 zygote32를 base로 뜰건지 알아야 하는데,

Process.java

에서

openZygoteSocketIfNeeded(String abi)

처럼 abi가 넘어가게 된다.

이 abi값은 proces start에 argument로 넘어오는데,

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

3192            Process.ProcessStartResult startResult = Process.start(entryPoint,
3193                    app.processName, uid, uid, gids, debugFlags, mountExternal,
3194                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
3195                    app.info.dataDir, entryPointArgs);

3177            String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
3178            if (requiredAbi == null) {
3179                requiredAbi = Build.SUPPORTED_ABIS[0];
3180            }
3181
3182            String instructionSet = null;
3183            if (app.info.primaryCpuAbi != null) {
3184                instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
3185            }


에서 requiredAbi argument가 있고... default abi를 사용하긴 하나, 결국 package가 지원하는 abi를 알아야 한다.



/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

5726                    final int copyRet;
5727                    if (isAsec) {
5728                        copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
5729                    } else {
5730                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
5731                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
5732                    }
5733
5734                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
5735                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
5736                                "Error unpackaging native libs for app, errorCode=" + copyRet);
5737                    }
5738
5739                    if (copyRet >= 0) {
5740                        pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
5741                    } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
5742                        pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
5743                    } else if (needsRenderScriptOverride) {
5744                        pkg.applicationInfo.primaryCpuAbi = abiList[0];
5745                    }


findSupportedAbi() 

com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz,

=> 413static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {


결국 "/lib/<abi-name>"이란 규칙을 이용해서 apk가 지원하는 ABI를 찾는다.

그런데, PackageManagerService를 보면, package가 '.apk' file인 경우 - Monolithic apk - 와 directory이 경우 - Cluster apk - 두 가지로 나뉘어 지는 것을 알 수 있다.

두 경우 모두 cpu Abi를 결정하기 위해서 위의 방법을 사용하는데, 두 경우 모두


<package file/dir>/lib/<cpu abi>


형태의 구성을 가정하고 있다.

따라서, apk build 자체가 위의 구성으로 나오는 package build와 실제 so는 system에 두고 link만들 거는 system apk 모두를 cover하고 있다.


ex.

Monolithic


Gallery.apk

|-AndroidManifest.xml

|-...

|-lib/armeabi-v7a/xxx.so

|...




Cluster (PDK build시 "/system/app/<package name>" directory)


Gallery/ ('/system/app/Gallery')

|-Gallery.apk

|-x86_64/Gallery.odex

|-lib/x86_64/xxx.so



두 경우 모두 "<Gallery apk/dir>/lib/<cpu abi>"형태로 구성되고 이를 보고 판단한다.


아래 코드는 추가적으로 참고하자..

5686                    if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
5687                        if (isAsec) {
5688                            abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
5689                        } else {
5690                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
5691                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
5692                                    useIsaSpecificSubdirs);
5693                        }
5694                    }
5695
5696                    maybeThrowExceptionForMultiArchCopy(
5697                            "Error unpackaging 64 bit native libs for multiarch app.", abi64);
5698
5699                    if (abi64 >= 0) {
5700                       


뭐 결국은, apk의 "/lib/<abi-name>"이 platform이 지원하는 64bit abi에 포함되면, 64bit apk로 판단하는 방식이다...

즉, APK가 어떤 abi를 지원하는지는 manifest file 등등 이런 곳에 따로 기록되어 있는것이 아니고, native library의 "/lib/<abi-name>"규칙에 의존해서 찾는다.


Platform build시 jni library들은 default로 모두 'system/lib'쪽에 들어가게 되어 있는데 - APK에 lib이 포함되지 않는다. - 그럼, 해당 APK의 ABI어떻게 알 수 있는가?

(최초 booting시 해당 APK를 AOT(Ahead Of Time) compile을 위해서는 어떤 target ABI로 compile할지가 정해져야 한다.)


일단 생각해보면, 어차피 VM(DEX)에만 의존하는 APK라면 특별히 ABI에 영향을 받지 않으므로 그냥 platform default ABI로 인식하고, AOT compile을 수행하면 된다. 그렇지만, 사용하는 native library들이 있고, 이 library를 load해야하는 APK라면, ABI에 따라서 어떤 zygote(32bit 혹은 64bit)에서 fork될 것인가를 결정해야 하므로, ABI를 system이 알아야할 필요가 있다.


이 경우는, 'LOCAL_MULTILIB := 32' 같은 방식으로 second abi를 사용하도록 정의하고 빌드하면, 'system/app/<package>'쪽에 보면


*** x86_64 용 TARGET ABI로 빌드하고, 'LOCAL_MULTILIB := 32'를 사용하는 Gallery2를 예로 들면...

Gallery2

- Gallery2.apk

- lib/x86/<xxx>.so -> /system/lib/<xxx>.so (symbolic link)

- x86/Gallery2.odex



즉, 제대로 32bit 용으로 빌드되어 있다. (그렇지만, Gallery2.apk내부에는 'lib/<abi>' 형태의 directory가 없다.

그렇지만, 만약, 32bit용으로 DEX가 pre-optimization되어 있지 않다면, 어떻게 Gallery2가 32bit용 package인지 system이 알고 32bit용으로 dex를 optimization - ART의 경우 AOT compile - 시킬수 있을까?



/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

6485        } else {
6486            // Cluster install
6487            info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
6488            info.nativeLibraryRootRequiresIsa = true;
6489
6490            info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
6491                    getPrimaryInstructionSet(info)).getAbsolutePath();
6492
6493            if (info.secondaryCpuAbi != null) {
6494                info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir,
6495                        VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath();
6496            }
6497        }
6498    }




scanPackageDirtyLI() -> 여기서 installer를 이용해서 app data directory / lib directory 등을 만든다. nativeFindSupportedAbi => copyNativeBinariesForSupportedAbi : 여기서 abi를 가지고 instrunction set directory로 copy한다. 이때 nativeLibraryRootDir 로 복사하는데, 이 nativeLibraryRootDir 로 될 수 있는게 <bundled app> 은 final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info); [[ Monolithic ]] Monolithic && bundled "/system/lib[64]/<apkname>" Monolithic 이고 Non-bundled "/data/app-lib/" 이다 [[ Cluster ]] info.nativeLibraryDir = new File(info.nativeLibraryRootDir, getPrimaryInstructionSet(info)).getAbsolutePath(); if (info.secondaryCpuAbi != null) { info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir, VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath(); } 즉 Cluster 의 경우 NativeLibraryDir은 abi가 아니라 instruction set 이름으로 찾아들어간다. VMRuntime.java 38 static { 39 ABI_TO_INSTRUCTION_SET_MAP.put("armeabi", "arm"); 40 ABI_TO_INSTRUCTION_SET_MAP.put("armeabi-v7a", "arm"); 41 ABI_TO_INSTRUCTION_SET_MAP.put("mips", "mips"); 42 ABI_TO_INSTRUCTION_SET_MAP.put("mips64", "mips64"); 43 ABI_TO_INSTRUCTION_SET_MAP.put("x86", "x86"); 44 ABI_TO_INSTRUCTION_SET_MAP.put("x86_64", "x86_64"); 45 ABI_TO_INSTRUCTION_SET_MAP.put("arm64-v8a", "arm64"); 46 }


VMRuntime.getInstructionSet(info.secondaryCpuAbi)) 에서 arch이름을 찾는 규칙이 위와 같다.

즉 cpuabi 는 instruction set 이름을 찾기 위해서 사용되지 그 자체는 package manager service에서 사용되지 않는다.

재미있는 것은 build system에서는

"cpu abi" -> "arch"

오 가타은 이름을 사용하고 있고, package manager service에서는

"cpu abi" -> "instruction set"

을 사용한다.

즉, 같은 이름을 build system과 package manager service에서, 'arch', 'instruction set'이라는 이름으로 서로 다르게 사용하고 있다.




실제 emulator를 실행시킨 후 위의 - 첫번째 booting에서 apk가 install된다. - package의 data directory를 가 보면.


$ ls -l /data/data/<package-name>

lib -> /data/app-lib/<package-name>


ex.

root@generic_x86_64:/data/data/com.android.gallery # ls -l lrwxrwxrwx install install 2014-11-10 14:13 lib -> /data/app-lib/com.android.gallery


그리고 아래의 code를 보자.


/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

6477            } else {
6478                final String apkName = deriveCodePathName(codePath);
6479                info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
6480                        .getAbsolutePath();
6481            }


위의 코드에서 'mAppLib32InstallDir'이 바로 '/data/app-lib' directory이다.

이렇게 자신의 library를 찾아가게 된다.







신고
Trackbacks 2 : Comments 2

티스토리 툴바