อุปกรณ์ Android ที่แตกต่างกันใช้ CPU ที่แตกต่างกัน ซึ่งรองรับชุดคำสั่งที่แตกต่างกัน การผสมผสานระหว่าง CPU และชุดคำสั่งแต่ละชุดจะมีอินเทอร์เฟซแบบไบนารีของแอปพลิเคชัน (ABI) ของตัวเอง ABI ประกอบด้วยข้อมูลต่อไปนี้
- ชุดคำสั่ง CPU (และส่วนขยาย) ที่ใช้ได้
- Endianness ของการจัดเก็บและการโหลดหน่วยความจำในระหว่างรันไทม์ Android จะเป็นแบบ Little-Endian เสมอ
- ข้อกำหนดสำหรับการส่งข้อมูลระหว่างแอปพลิเคชันกับระบบ ซึ่งรวมถึงข้อจำกัดด้านการจัดแนว และวิธีที่ระบบใช้สแต็กและรีจิสเตอร์เมื่อเรียกใช้ฟังก์ชัน
- รูปแบบของไบนารีที่เรียกใช้งานได้ เช่น โปรแกรมและไลบรารีที่แชร์ รวมถึงประเภทเนื้อหาที่รองรับ Android ใช้ ELF เสมอ ดูข้อมูลเพิ่มเติมได้ที่ ELF System V อินเทอร์เฟซแบบไบนารีของแอปพลิเคชัน
- วิธีที่ชื่อ C++ ถูก Mangled ดูข้อมูลเพิ่มเติมได้ที่ Generic/Itanium C++ ABI
หน้านี้แสดงรายการ ABI ที่ NDK รองรับ และให้ข้อมูลเกี่ยวกับวิธีที่ ABI แต่ละรายการทำงาน
ABI ยังหมายถึง Native API ที่แพลตฟอร์มรองรับด้วย ดูรายการปัญหาเกี่ยวกับ ABI ประเภทดังกล่าวที่ส่งผลต่อระบบ 32 บิตได้ที่ ข้อบกพร่องของ ABI แบบ 32 บิต
ABI ที่รองรับ
ตารางที่ 1 ABI และชุดคำสั่งที่รองรับ
| ABI | ชุดคำสั่งที่รองรับ | หมายเหตุ |
|---|---|---|
armeabi-v7a |
|
ใช้ร่วมกับอุปกรณ์ ARMv5/v6 ไม่ได้ |
arm64-v8a |
Armv8.0 เท่านั้น | |
x86 |
ไม่รองรับ MOVBE หรือ SSE4 | |
x86_64 |
|
x86-64-v2 แบบเต็ม |
หมายเหตุ: ในอดีต NDK รองรับ ARMv5 (armeabi) รวมถึง MIPS แบบ 32 บิตและ 64 บิต แต่การรองรับ ABI เหล่านี้ถูกนำออกใน NDK r17
armeabi-v7a
ABI นี้ใช้สำหรับ CPU ARM แบบ 32 บิต ซึ่งรวมถึง Thumb-2 และ Neon
ดูข้อมูลเกี่ยวกับส่วนต่างๆ ของ ABI ที่ไม่ได้เฉพาะเจาะจงสำหรับ Android ได้ที่ Application Binary Interface (ABI) for the ARM Architecture
ระบบบิลด์ของ NDK จะสร้างโค้ด Thumb-2 โดยค่าเริ่มต้น เว้นแต่คุณจะใช้
LOCAL_ARM_MODE ใน Android.mk สำหรับ
ndk-build หรือ ANDROID_ARM_MODE เมื่อกำหนดค่า CMake
ดูข้อมูลเพิ่มเติมเกี่ยวกับประวัติของ Neon ได้ที่ Neon Support
ABI นี้ใช้ -mfloat-abi=softfp ด้วยเหตุผลทางประวัติศาสตร์ ซึ่งทำให้ค่า float
ทั้งหมดถูกส่งผ่านในรีจิสเตอร์จำนวนเต็ม และค่า double ทั้งหมดถูกส่งผ่าน
ในรีจิสเตอร์จำนวนเต็มแบบคู่เมื่อทำการเรียกใช้ฟังก์ชัน แม้ว่าจะมีชื่อเช่นนี้ แต่การดำเนินการนี้จะส่งผลต่อ
ข้อกำหนดการเรียก จุดลอยตัวเท่านั้น โดยคอมไพเลอร์จะยังคง
ใช้คำสั่งจุดลอยตัวของฮาร์ดแวร์สำหรับการคำนวณ
ABI นี้ใช้ long double แบบ 64 บิต (IEEE binary64 ซึ่งเหมือนกับ double)
arm64-v8a
ABI นี้ใช้สำหรับ CPU ARM แบบ 64 บิต
ดูรายละเอียดทั้งหมดเกี่ยวกับส่วนต่างๆ ของ ABI ที่ไม่ได้เฉพาะเจาะจงสำหรับ Android ได้ที่ Arm's Learn the Architecture นอกจากนี้ Arm ยังมีคำแนะนำในการพอร์ตบางอย่างใน 64-bit Android Development
คุณสามารถใช้ Neon Intrinsics ในโค้ด C และ C++ เพื่อใช้ประโยชน์จากส่วนขยาย Advanced SIMD The Neon Programmer's Guide for Armv8-A มีข้อมูลเพิ่มเติมเกี่ยวกับ Neon Intrinsics และการเขียนโปรแกรม Neon โดยทั่วไป
ใน Android รีจิสเตอร์ x18 ที่เฉพาะเจาะจงสำหรับแพลตฟอร์มจะสงวนไว้สำหรับ
ShadowCallStack
และโค้ดของคุณไม่ควรแตะต้องรีจิสเตอร์นี้ Clang เวอร์ชันปัจจุบันจะใช้ตัวเลือก -ffixed-x18 ใน Android โดยค่าเริ่มต้น ดังนั้นคุณจึงไม่จำเป็นต้องกังวลเกี่ยวกับเรื่องนี้ เว้นแต่คุณจะมีแอสเซมเบลอร์ที่เขียนด้วยมือ (หรือคอมไพเลอร์ที่เก่ามาก)
ABI นี้ใช้ 128 บิต long double (IEEE binary128)
x86
ABI นี้ใช้สำหรับ CPU ที่รองรับชุดคำสั่งที่เรียกกันโดยทั่วไปว่า "x86", "i386" หรือ "IA-32"
ABI ของ Android มีชุดคำสั่งพื้นฐาน รวมถึงส่วนขยาย MMX, SSE, SSE2, SSE3 และ SSSE3
ABI ไม่รวมส่วนขยายชุดคำสั่ง IA-32 ที่เป็นตัวเลือกอื่นๆ เช่น MOVBE หรือ SSE4 เวอร์ชันต่างๆ คุณยังคงใช้ส่วนขยายเหล่านี้ได้ ตราบใดที่คุณใช้การตรวจสอบฟีเจอร์ในระหว่างรันไทม์เพื่อเปิดใช้ส่วนขยาย และมีฟอลแบ็กสำหรับอุปกรณ์ที่ไม่รองรับ
เครื่องมือ Toolchain ของ NDK จะถือว่ามีการจัดแนวสแต็ก 16 ไบต์ก่อนการเรียกใช้ฟังก์ชัน เครื่องมือและตัวเลือกเริ่มต้นจะบังคับใช้กฎนี้ หากคุณเขียนโค้ดแอสเซมบลี คุณต้องตรวจสอบว่าได้รักษาการจัดแนวสแต็กไว้ และตรวจสอบว่าคอมไพเลอร์อื่นๆ ปฏิบัติตามกฎนี้ด้วย
ดูรายละเอียดเพิ่มเติมได้ในเอกสารต่อไปนี้
- ข้อกำหนดการเรียกใช้สำหรับคอมไพเลอร์ C++ และระบบปฏิบัติการต่างๆ
- Intel IA-32 Intel Architecture Software Developer's Manual, Volume 2: Instruction Set Reference
- Intel IA-32 Intel Architecture Software Developer's Manual, Volume 3: System Programming Guide
- System V Application Binary Interface: Intel386 Processor Architecture Supplement
ABI นี้ใช้ long double แบบ 64 บิต (IEEE binary64 ซึ่งเหมือนกับ double และไม่ใช่ long double แบบ 80 บิตที่ใช้เฉพาะใน Intel ซึ่งพบได้
บ่อยกว่า)
x86_64
ABI นี้ใช้สำหรับ CPU ที่รองรับชุดคำสั่งที่เรียกกันโดยทั่วไปว่า "x86-64-v2"
ABI ของ Android มีชุดคำสั่งพื้นฐาน รวมถึง MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 และ คำสั่ง POPCNT
ABI ไม่รวมส่วนขยายชุดคำสั่ง x86-64 ที่เป็นตัวเลือกอื่นๆ เช่น MOVBE, SHA หรือ AVX เวอร์ชันต่างๆ คุณยังคงใช้ส่วนขยายเหล่านี้ได้ ตราบใดที่คุณใช้การตรวจสอบฟีเจอร์ในระหว่างรันไทม์เพื่อเปิดใช้ส่วนขยาย และมีฟอลแบ็กสำหรับอุปกรณ์ที่ไม่รองรับ
ดูรายละเอียดเพิ่มเติมได้ในเอกสารต่อไปนี้
- ข้อกำหนดการเรียกใช้สำหรับคอมไพเลอร์ C++ และระบบปฏิบัติการต่างๆ
- Intel64 and IA-32 Architectures Software Developer's Manual, Volume 2: Instruction Set Reference
- Intel64 and IA-32 Intel Architecture Software Developer's Manual Volume 3: System Programming
ABI นี้ใช้ 128 บิต long double (IEEE binary128)
สร้างโค้ดสำหรับ ABI ที่เฉพาะเจาะจง
Gradle
Gradle (ไม่ว่าจะใช้ผ่าน Android Studio หรือจากบรรทัดคำสั่ง) จะสร้าง ABI ทั้งหมดที่ไม่เลิกใช้งานแล้วโดยค่าเริ่มต้น หากต้องการจำกัดชุด ABI ที่แอปพลิเคชันรองรับ ให้ใช้ abiFilters ตัวอย่างเช่น หากต้องการสร้างเฉพาะ ABI แบบ 64 บิต ให้ตั้งค่าการกำหนดค่าต่อไปนี้ใน build.gradle
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-build
ndk-build จะสร้าง ABI ทั้งหมดที่ไม่เลิกใช้งานแล้วโดยค่าเริ่มต้น คุณสามารถกำหนดเป้าหมาย ABI ที่เฉพาะเจาะจงได้โดยการตั้งค่า APP_ABI ในไฟล์ Application.mk ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการใช้ APP_ABI
APP_ABI := arm64-v8a # Target only arm64-v8a
APP_ABI := all # Target all ABIs, including those that are deprecated.
APP_ABI := armeabi-v7a x86_64 # Target only armeabi-v7a and x86_64.
ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าที่คุณระบุได้สำหรับ APP_ABI ได้ที่
Application.mk
CMake
เมื่อใช้ CMake คุณจะสร้าง ABI ได้ครั้งละ 1 รายการ และต้องระบุ ABI อย่างชัดเจน คุณทำได้โดยใช้ตัวแปร ANDROID_ABI ซึ่งต้องระบุในบรรทัดคำสั่ง (ตั้งค่าใน CMakeLists.txt ไม่ได้) เช่น
$ cmake -DANDROID_ABI=arm64-v8a ...
$ cmake -DANDROID_ABI=armeabi-v7a ...
$ cmake -DANDROID_ABI=x86 ...
$ cmake -DANDROID_ABI=x86_64 ...
ดูแฟล็กอื่นๆ ที่ต้องส่งไปยัง CMake เพื่อสร้างด้วย NDK ได้ที่ คู่มือ CMake
ลักษณะการทำงานเริ่มต้นของระบบบิลด์คือการรวมไบนารีสำหรับ ABI แต่ละรายการไว้ใน APK เดียว ซึ่งเรียกอีกอย่างว่า Fat APK Fat APK มีขนาดใหญ่กว่า APK ที่มีเฉพาะไบนารีสำหรับ ABI เดียวอย่างมาก ข้อดีคือมีความเข้ากันได้ที่กว้างขึ้น แต่ข้อเสียคือ APK มีขนาดใหญ่ขึ้น เราขอแนะนำอย่างยิ่ง ให้คุณใช้ประโยชน์จาก App Bundle หรือ APK Split เพื่อ ลดขนาด APK ในขณะที่ยังคงรักษาความเข้ากันได้กับอุปกรณ์ สูงสุด
ในระหว่างการติดตั้ง ตัวจัดการแพ็กเกจจะคลายแพ็กเฉพาะรหัสเครื่องที่เหมาะสมที่สุดสำหรับอุปกรณ์เป้าหมาย ดูรายละเอียดได้ที่การแยกโค้ดแบบเนทีฟโดยอัตโนมัติ ในระหว่างการติดตั้ง
การจัดการ ABI ในแพลตฟอร์ม Android
ส่วนนี้ให้รายละเอียดเกี่ยวกับวิธีที่แพลตฟอร์ม Android จัดการโค้ดที่มาพร้อมเครื่องใน APK
โค้ดแบบเนทีฟในแพ็กเกจแอป
ทั้ง Play Store และตัวจัดการแพ็กเกจคาดว่าจะพบไลบรารีที่สร้างโดย NDK ในเส้นทางไฟล์ภายใน APK ที่ตรงกับรูปแบบต่อไปนี้
/lib/<abi>/lib<name>.so
โดยที่ <abi> คือชื่อ ABI รายการใดรายการหนึ่งที่แสดงใน ABI ที่รองรับ,
และ <name> คือชื่อไลบรารีตามที่คุณกำหนดไว้สำหรับตัวแปร LOCAL_MODULE
ในไฟล์ Android.mk เนื่องจากไฟล์ APK เป็นเพียงไฟล์ ZIP จึงเปิดไฟล์เหล่านี้และยืนยันว่าไลบรารีที่แชร์ที่มาพร้อมเครื่องอยู่ในตำแหน่งที่ถูกต้องได้ง่าย
หากระบบไม่พบไลบรารีที่แชร์ที่มาพร้อมเครื่องในตำแหน่งที่คาดไว้ ระบบจะใช้ไลบรารีเหล่านั้นไม่ได้ ในกรณีดังกล่าว แอปเองจะต้องคัดลอกไลบรารี และจากนั้นจึงเรียกใช้ dlopen()
ใน Fat APK ไลบรารีแต่ละรายการจะอยู่ในไดเรกทอรีที่มีชื่อตรงกับ ABI ที่เกี่ยวข้อง ตัวอย่างเช่น Fat APK อาจมีรายการต่อไปนี้
/lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
การรองรับ ABI ของแพลตฟอร์ม Android
ระบบ Android จะทราบในระหว่างรันไทม์ว่าระบบรองรับ ABI ใดบ้าง เนื่องจากพร็อพเพอร์ตี้ระบบที่เฉพาะเจาะจงสำหรับบิลด์จะระบุข้อมูลต่อไปนี้
- ABI หลักสำหรับอุปกรณ์ ซึ่งสอดคล้องกับรหัสเครื่องที่ใช้ในอิมเมจระบบเอง
- ABI รอง (ไม่บังคับ) ซึ่งสอดคล้องกับ ABI อื่นๆ ที่อิมเมจระบบ รองรับด้วย
กลไกนี้ช่วยให้มั่นใจได้ว่าระบบจะแยกโค้ดเครื่องที่ดีที่สุดออกจากแพ็กเกจในระหว่างการติดตั้ง
คุณสามารถบังคับติดตั้ง APK สำหรับ ABI ที่เฉพาะเจาะจงได้ ซึ่งอาจมีประโยชน์สำหรับการทดสอบในอุปกรณ์ที่รองรับ ABI มากกว่า 1 รายการ ใช้คำสั่งต่อไปนี้
adb install --abi abi-identifier path_to_apk
การแยกโค้ดแบบเนทีฟโดยอัตโนมัติในระหว่างการติดตั้ง
เมื่อติดตั้งแอปพลิเคชัน บริการตัวจัดการแพ็กเกจจะสแกน APK และมองหาไลบรารีที่แชร์ในรูปแบบต่อไปนี้
lib/<primary-abi>/lib<name>.so
หากไม่พบไลบรารีที่แชร์ในรูปแบบดังกล่าว และคุณได้กำหนด ABI รองไว้ บริการจะสแกนหาไลบรารีที่แชร์ในรูปแบบต่อไปนี้
lib/<secondary-abi>/lib<name>.so
เมื่อพบไลบรารีที่ต้องการ เครื่องมือจัดการแพ็กเกจจะคัดลอก ไลบรารีเหล่านั้นไปยัง /lib/lib<name>.so ในไดเรกทอรีไลบรารีแบบเนทีฟของแอปพลิเคชัน (<nativeLibraryDir>/) ข้อมูลโค้ดต่อไปนี้จะดึงข้อมูล nativeLibraryDir
Kotlin
import android.content.pm.PackageInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager ... val ainfo = this.applicationContext.packageManager.getApplicationInfo( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ) Log.v(TAG, "native library dir ${ainfo.nativeLibraryDir}")
Java
import android.content.pm.PackageInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; ... ApplicationInfo ainfo = this.getApplicationContext().getPackageManager().getApplicationInfo ( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ); Log.v( TAG, "native library dir " + ainfo.nativeLibraryDir );
หากไม่มีไฟล์ออบเจ็กต์ที่แชร์เลย แอปพลิเคชันจะสร้างและติดตั้งได้ แต่จะเกิดข้อขัดข้องในระหว่างรันไทม์
ARMv9: การเปิดใช้ PAC และ BTI สำหรับ C/C++
การเปิดใช้ PAC/BTI จะช่วยป้องกันเวกเตอร์การโจมตีบางอย่าง PAC จะปกป้องที่อยู่ส่งคืนโดยการลงชื่อแบบเข้ารหัสลับในบทนำของฟังก์ชัน และตรวจสอบว่าที่อยู่ส่งคืนยังคงลงชื่ออย่างถูกต้องในบทสรุป BTI จะป้องกันการข้ามไปยังตำแหน่งที่กำหนดเองในโค้ดโดยกำหนดให้เป้าหมายการแยกแต่ละรายการเป็นคำสั่งพิเศษที่ไม่ทำอะไรเลย แต่จะบอกโปรเซสเซอร์ว่าสามารถข้ามไปยังตำแหน่งนั้นได้
Android ใช้คำสั่ง PAC/BTI ที่ไม่ทำอะไรเลยในโปรเซสเซอร์รุ่นเก่าที่ไม่รองรับคำสั่งใหม่ เฉพาะอุปกรณ์ ARMv9 เท่านั้นที่จะมีการป้องกัน PAC/BTI แต่คุณสามารถเรียกใช้โค้ดเดียวกันในอุปกรณ์ ARMv8 ได้ด้วย โดยไม่จำเป็นต้องมีไลบรารีหลายเวอร์ชัน แม้ในอุปกรณ์ ARMv9 PAC/BTI จะใช้ได้กับโค้ด 64 บิตเท่านั้น
การเปิดใช้ PAC/BTI จะทำให้ขนาดโค้ดเพิ่มขึ้นเล็กน้อย โดยปกติจะอยู่ที่ 1%
ดูคำอธิบายโดยละเอียดเกี่ยวกับเวกเตอร์การโจมตีที่ PAC/BTI กำหนดเป้าหมาย และวิธีที่การป้องกันทำงานได้ที่ Learn the architecture - Providing protection for complex software (PDF) ของ Arm
การเปลี่ยนแปลงบิลด์
ndk-build
ตั้งค่า LOCAL_BRANCH_PROTECTION := standard ในแต่ละโมดูลของ Android.mk
CMake
ใช้ target_compile_options($TARGET PRIVATE -mbranch-protection=standard) สำหรับแต่ละเป้าหมายใน CMakeLists.txt
ระบบบิลด์อื่นๆ
คอมไพล์โค้ดโดยใช้ -mbranch-protection=standard แฟล็กนี้จะทำงานเมื่อคอมไพล์สำหรับ ABI arm64-v8a เท่านั้น คุณไม่จำเป็นต้องใช้แฟล็กนี้เมื่อลิงก์
การแก้ปัญหา
เราไม่พบปัญหาใดๆ เกี่ยวกับการรองรับ PAC/BTI ของคอมไพเลอร์ แต่มีข้อควรระวังดังนี้
- ระมัดระวังไม่ให้รวมโค้ด BTI และโค้ดที่ไม่ใช่ BTI เข้าด้วยกันเมื่อลิงก์ เนื่องจากจะทำให้ไลบรารีไม่มีการป้องกัน BTI ที่เปิดใช้อยู่ คุณสามารถใช้ llvm-readelf เพื่อตรวจสอบว่าไลบรารีที่ได้มีโน้ต BTI หรือไม่
$ llvm-readelf --notes LIBRARY.so
[...]
Displaying notes found in: .note.gnu.property
Owner Data size Description
GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0 (property note)
Properties: aarch64 feature: BTI, PAC
[...]
$
OpenSSL เวอร์ชันเก่า (ก่อน 1.1.1i) มีข้อบกพร่องในแอสเซมเบลอร์ที่เขียนด้วยมือซึ่งทำให้ PAC ล้มเหลว โปรดอัปเกรดเป็น OpenSSL เวอร์ชันปัจจุบัน
ระบบ DRM ของแอปบางระบบในเวอร์ชันเก่าจะสร้างโค้ดที่ละเมิดข้อกำหนด PAC/BTI หากคุณใช้ DRM ของแอปและพบปัญหาเมื่อเปิดใช้ PAC/BTI โปรดติดต่อผู้ให้บริการ DRM เพื่อขอเวอร์ชันที่แก้ไขแล้ว