참조 : ABI Management

실무를 하면서 네이티브 라이브러리를 쓰려다 ABI관련 에러를 만나게 된적이 있는데, 안드로이드에서 ABI를 어떻게 관리하는지 정리해보기로 한다. 내용은 안드로이드 developer 사이트 내용을 정리 해 보았다.

안드로이드 디바이스는 제조사의 사정에 따라서 입맛대로 CPU를 골라 쓸 수 있다. 이쪽에서 가장 대표적인 ARM을 비롯하여 MIPS, x86을 지원한다. 이들이 사용하는 명령 세트는 모두 다르며, 각 아키텍쳐 – 명령세트의 조합은 자신들에게 맞는 ABI(Application Binary Interface)를 갖는다.

ABI란, 런타임에 시스템과 앱의 머신코드가 어떻게 상호작용할지를 기술한 인터페이스이다.  so파일을 로딩하는 경우, 머신코드-아키텍쳐에서 사용하는 ABI와 일치해야 구동이 가능하다. ARM 칩에서 x86 머신코드를 네이티브로 실행할 순 없지 않은가?

ABI는 보통 이런 정보들을 포함하고 있다.

  • 머신코드가 사용해야 하는 CPU 명령 세트.
  • 런타임에 사용할 메모리 로드/스토어 endianness.
  • ABI에서 지원하는 실행가능한 바이너리 포맷(프로그램,  shared lib)
  • 당신의 코드와 시스템간의 데이터 전달을 위한 다양한 컨벤션. 이 컨벤션들은 시스템이 함수호출 시 스택과 레지스터를 어떻게 사용할지 뿐만 아니라 alignment 제약사항까지 포함한다.
  • 일반적으로 매우 특정한 라이브러리들에서, 런타임시 당신의 머신코드에서 사용가능한 함수 심볼의 목록.

이 게시물에서는 NDK가 지원하는 ABI, ABI가 제공하는 정보, 그리고 ABI가어떻게 동작하는지를 얘기 해보고자 한다.

지원되는 ABI들

각 ABI는 1개이상의 명령 세트를 지원한다. 아래 표를 참고하면 된다.

ABI 명 지원되는
명령 세트(들)
설명
armeabi
  • ARMV5TE and later
  • Thumb-1
하드웨어 FPU 지원 없음.
armeabi-v7a

(armeabi-v7a-hard)

  • armeabi
  • Thumb-2
  • VFPv3-D16
  • Other, optional
armeabi-v7a-hard가 있는경우 하드웨어 FPU 지원.
ARMv5, v6와 호환되지 않음.
arm64-v8a
  • AArch-64
x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
movbe 또는 SSE4는 지원 안함.
x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • POPCNT
mips
  • MIPS32r1 and later
하드웨어 FPU를 사용하는 경우 최대의 호환성을 위해 CPU:FPU의 클럭비가 2:1임을 가정.  micromips와 mips16은 지원하지 않음.
mips64
  • MIPS64r6

자세한 ABI별 설명은 아래에 기술한다.

armeabi

armeabi는 최소 ARMv5TE 명령세트를 사용하는 ARM 기반 CPU부터 지원하기위해 존재한다. ARMv5TE는 무려 99년도에 나온 명령세트로, 안드로이드라는 플랫폼만을 한정 할 때, 사실상 모든 ARM칩에서 구동 가능한 ABI라고 보면 된다.

호환성이 가장 중시되므로 하드웨어적인 지원은 가장 떨어진다. 하드웨어 FPU 사용이 불가능하며, 모든 fp 명령은 소프트웨어 방식으로 처리해야 한다. (libgcc.a static lib)

Thumb 명령세트(Thumb-1)를 지원한다.

armeabi-v7a (armeabi-v7a-hard)

모든 ARM과 호환되는 armeabi를 확장하여 몇 가지 명령세트가 추가된 형태의 ABI 이다.  아래는 추가된 확장명령세트에 대한 설명이다.

  • Thumb-2 확장명령세트는 32bit ARM 명령들과 성능은 비슷하면서 명령의 사이즈는 Thumb-1과 비슷하다.
  • VFP 하드웨어 FPU 명령. 더 구체적으로 얘기하자면,  VFPv3-D16의 경우 64비트 크기의 FP전용 레지스터를 16개 가지며, 추가적으로 ARM 코어로부터 16개의 32비트 크기 레지스터를 갖게 된다.

v7a는 또한, NEON(ARM 아키텍쳐에서의 SIMD instruction), VFPv3-D32, ThumbEE 명령세트들도 포함된다.

armeabi-v7a-hard

armeabi-v7a의 변형으로, NDK에서는 armeabi-v7a와 구분된다. 기본적으로 아래와같은 플래그가 armeabi-v7a에서 더 붙게 된다.

성능을 위해서 하드웨어 FPU 사용을 강제해야할 경우에 이 ABI를 사용할 수 있다.

arm64-v8a

ARM의 64비트 명령세트인 AArch64를 사용한다. NEON과 VFPv4를 지원한다.

x86

x86이라는 단어는 일반적으로 IA-32와 동치다. 즉 인텔의 32비트 프로세서를 의미한다.

gcc 컴파일러에서는 일반적으로 아래 플래그를 통해 컴파일한다.

이는 i686(Pentium  Pro) 명령세트를 통해 빌드하겠다는 의미이며, MMX, SSE, SSE2, SSE3, SSSE3까지 사용가능한 프로세서를 타겟으로 한다.

이 ABI에서는 아래 두 명령은 사용이 불가능하다.

  • MOVBE (big-endian move. Haswell 이상에서 지원.)
  • SSE4.x

x86_64

x86의 64비트 버전이다. (x86_64 != IA-64 && x86_64 == AMD64인건 다들 알것이라 생각한다.)

gcc 컴파일러에서는 일반적으로 아래 플래그를 통해 컴파일한다.

이는 x86-64 아키텍쳐로 빌드하되 MMX, SSE, SSE2, SSE3, SSSE3, SSE4.x, POPCNT를 포함한다.

이 ABI에서도 사용못하는 명령세트들이 있다. 다들 너무 최신명령들이라 그렇다.

  • MOVBE
  • SHA
  • AVX
  • AVX2

mips, mips64

MIPS의 경우 mips32r1 명령세트 기반 프로세서부터 사용가능하다. 아래와 같은 기능들이 지원된다.

  • MIPS32 revision 1 ISA
  • Little-endian
  • O32
  • Hard-float
  • No DSP application-specific extensions

mips64는 MIPS 64비트 버전 명령세트인 MIPS64 R6 기반 프로세서에서 사용가능하다.

특정 ABI로 코드 생성하기

기본적으로 NDK에서는 armeabi ABI에서 동작하는 코드를 생성한다. 다른 ABI를 사용하고 싶다면 Application.mk 파일을 수정하면 된다.

이렇게 하면 armeabi, armeabi-v7a 두가지의 so 파일이 빌드 될 것이다. 여러개의 ABI를 지원하는 것은 호환성에서는 유리하지만 같은 so파일이 중복으로 생성되므로 앱 크기에서는 매우 불리한 일이다. 따라서 앱 크기와 호환성간의 트레이드오프를 잘 생각해야 할 것이다.

안드로이드 플랫폼에서의 ABI 관리

가장 중요한 내용으로, ABI가 시스템에서 어떻게 관리되고 각 CPU 아키텍쳐와 어떻게 매핑되어 실행하는지를 설명한다.

앱 패키지에서의 네이티브 코드

플레이스토어와 패키지매니저는 NDK로 생성된 라이브러리를 아래와 같은 경로에 있음을 기대하고  패턴대로 검색하여 로드하게 된다.

여기서 <abi>는 안드로이드 상에서 지원되는 ABI 이름이며, <name>은 Android.mk 파일에서 LOCAL_MODULE 변수에 지정한 이름이 된다. 만약 호환성에 미친 앱같이 여러 ABI를 지원한다면 아래와 같이 되리라.

여기서 참고해야 할것은 ARMv7기반의 안드로이드 디바이스들은 안드로이드 버전 4.0.3 이하 버전에서는 armeabi, armeabi-v7a 디렉토리가 같이 존재하여도 armeabi를 무조건 사용한다는 이슈가 있다. 이는 4.0.4에서는 해결된 사항이다.

안드로이드 플랫폼의 ABI 지원

안드로이드 시스템은 런타임에 어떤  ABI를 지원하는지를 알고 있는데, 이는 특정 빌드환경 시스템의 속성들이 다음사항을 알려주기 때문이다.

  • 해당 디바이스의 primary ABI는 시스템 이미지 그 자체에 사용되는 머신코드와 일치하며
  • 선택적으로 secondary ABI는 시스템 이미지가 또한 지원하는 또다른 ABI 와 일치한다.

가장 최고의 성능을 위해서는 디바이스의 primary ABI로 직접 코드를 컴파일 하는 것이다. 가령 대부분의 ARMv7 기반 디바이스라면 굳이 쓸데없는 고퀄리티의 호환성을 위해 armeabi를 고집할 필요가 없다.

많은 x86 기반 디바이스들은 armeabi-v7a와 armeabi를 구동할 수 있다. 이러한 디바이스들의 secodnary abi가 armeabi-v7a이기 때문이다.

대부분의 MIPS 기반 디바이스들은 primary abi가 mips이다.

앱 install time에 네이티브 코드 자동 extraction

앱 설치시, 패키지 매니저 서비스는 APK를 스캔하여, 아래와 같은 패턴으로 shared lib를 찾는다. 처음엔 primary ABI를 대입해 찾는다.

만약 찾지 못한다면 secondary ABI를 대입해 찾아본다.

라이브러리가 검색되면, 패키지 매니저는 /lib/lib<name>.so 파일을 data 디렉토리에 복사한다. (data/data/<package_name>/lib/)

만약 shared lib를 못찾으면? 앱 설치는 되겠지만 런타임에 크래시가 발생한다.