ABI ของ Android

อุปกรณ์ Android แต่ละประเภทใช้ CPU ที่แตกต่างกัน จึงรองรับชุดวิธีการที่แตกต่างกันด้วย CPU และชุดคำสั่งแต่ละชุดมีอินเทอร์เฟซแบบไบนารีของแอปพลิเคชัน (ABI) ของตัวเอง ABI มีข้อมูลต่อไปนี้

  • ชุดคำสั่ง (และส่วนขยาย) ของ CPU ที่ใช้งานได้
  • รูปแบบการจัดเก็บและโหลดหน่วยความจำที่รันไทม์ Android จะเป็นรูปแบบ Little Endian เสมอ
  • แบบแผนสำหรับการส่งข้อมูลระหว่างแอปพลิเคชันและระบบ รวมถึงข้อจำกัดในการจัดวาง และวิธีที่ระบบใช้สแต็กและลงทะเบียนเมื่อเรียกใช้ฟังก์ชันต่างๆ
  • รูปแบบของไฟล์ไบนารีที่เรียกใช้งานได้ เช่น โปรแกรมและไลบรารีที่ใช้ร่วมกัน และประเภทเนื้อหาที่รองรับ Android ใช้ ELF เสมอ ดูข้อมูลเพิ่มเติมได้ที่ ELF System V Application Binary Interface
  • วิธีเปลี่ยนชื่อ C++ ดูข้อมูลเพิ่มเติมได้ที่ Generic/Itanium C++ ABI

หน้านี้จะแสดง ABI ที่ NDK รองรับและให้ข้อมูลเกี่ยวกับวิธีการทำงานของ ABI แต่ละรายการ

ABI ยังอาจหมายถึง API เดิมที่แพลตฟอร์มรองรับด้วย ดูรายการปัญหา ABI ประเภทดังกล่าวที่ส่งผลกระทบต่อระบบ 32 บิตได้ที่ข้อบกพร่อง ABI แบบ 32 บิต

ABI ที่รองรับ

ตารางที่ 1 ABI และชุดคำสั่งที่รองรับ

ABI ชุดคำสั่งที่รองรับ หมายเหตุ
armeabi-v7a
  • Armeabi
  • Thumb-2
  • นีออน
  • ใช้ร่วมกับอุปกรณ์ ARMv5/v6 ไม่ได้
    arm64-v8a
  • AArch64
  • Armv8.0 เท่านั้น
    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
  • CMPXCHG16B
  • x86-64-v1 ทั้งหมด แต่ x86-64-v2 เพียงบางส่วน (ไม่มี LAHF-SAHF)

    หมายเหตุ: ก่อนหน้านี้ NDK รองรับ ARMv5 (armeabi) และ MIPS 32 บิตและ 64 บิต แต่เราได้นําการรองรับ ABI เหล่านี้ออกแล้วใน NDK r17

    armeabi-v7a

    ABI นี้มีไว้สำหรับ CPU ARM 32 บิต ซึ่งรวมถึง Thumb-2 และ Neon

    ดูข้อมูลเกี่ยวกับส่วนต่างๆ ของ ABI ที่ไม่ได้เจาะจง Android ได้ที่อินเทอร์เฟซแบบไบนารีของแอปพลิเคชัน (ABI) สำหรับสถาปัตยกรรม ARM

    ระบบการบิลด์ของ NDK จะสร้างโค้ด Thumb-2 โดยค่าเริ่มต้น เว้นแต่คุณจะใช้ LOCAL_ARM_MODE ใน Android.mk สำหรับ ndk-build หรือ ANDROID_ARM_MODE เมื่อกำหนดค่า CMake

    ดูข้อมูลเพิ่มเติมเกี่ยวกับประวัติของ Neon ได้ที่การสนับสนุน Neon

    ABI นี้ใช้ -mfloat-abi=softfp เนื่องจากเหตุผลทางประวัติศาสตร์ ซึ่งทำให้ระบบส่งค่า float ทั้งหมดในรีจิสเตอร์จำนวนเต็ม และส่งค่า double ทั้งหมดในคู่รีจิสเตอร์จำนวนเต็มเมื่อทำการเรียกใช้ฟังก์ชัน แม้ว่าจะมีชื่อเช่นนี้ แต่ตัวเลือกนี้จะส่งผลต่อรูปแบบการเรียกที่เป็นเลขทศนิยมเท่านั้น คอมไพเลอร์จะยังคงใช้คำสั่งเลขทศนิยมของฮาร์ดแวร์สำหรับการดำเนินการทางคณิตศาสตร์

    ABI นี้ใช้ long double 64 บิต (IEEE binary64 เหมือนกับ double)

    arm64-v8a

    ABI นี้มีไว้สำหรับ CPU แบบ ARM 64 บิต

    ดูรายละเอียดทั้งหมดของ ABI ที่ไม่ได้เจาะจง Android ได้ที่หัวข้อศึกษาสถาปัตยกรรมของ Arm Arm ยังให้คําแนะนําในการพอร์ตบางส่วนในการพัฒนา Android แบบ 64 บิต

    คุณสามารถใช้ Neon Intrinsics ในโค้ด C และ C++ เพื่อใช้ประโยชน์จากส่วนขยาย SIMD ขั้นสูง คู่มือโปรแกรมเมอร์ Neon สำหรับ Armv8-A มีข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันการทํางานภายในของ Neon และการเขียนโปรแกรม Neon โดยทั่วไป

    ใน Android รีจิสเตอร์ x18 สำหรับแพลตฟอร์มที่เฉพาะเจาะจงสงวนไว้สำหรับ ShadowCallStack และโค้ดของคุณไม่ควรแตะต้อง Clang เวอร์ชันปัจจุบันจะใช้ตัวเลือก -ffixed-x18 โดยค่าเริ่มต้นใน Android คุณจึงไม่ต้องกังวลเกี่ยวกับเรื่องนี้ เว้นแต่ว่าคุณจะมีแอสเซมเบลอร์ที่เขียนด้วยตนเอง (หรือคอมไพเลอร์รุ่นเก่ามาก)

    ABI นี้ใช้ long double แบบ 128 บิต (ไบนารี IEEE128)

    X86

    ABI นี้มีไว้สำหรับ CPU ที่รองรับชุดคำสั่งที่เรียกกันโดยทั่วไปว่า "x86", "i386" หรือ "IA-32"

    ABI ของ Android ประกอบด้วยชุดคำสั่งพื้นฐานและส่วนขยาย MMX, SSE, SSE2, SSE3 และ SSSE3

    ABI ไม่ได้รวมส่วนขยายชุดคำสั่ง IA-32 อื่นๆ ที่ไม่บังคับ เช่น MOVBE หรือ SSE4 รูปแบบต่างๆ คุณจะยังใช้ส่วนขยายเหล่านี้ได้ ตราบใดที่คุณใช้การสำรวจฟีเจอร์รันไทม์เพื่อเปิดใช้ส่วนขยายเหล่านั้น และระบุทางเลือกสําหรับอุปกรณ์ที่ไม่รองรับ

    เครื่องมือชุด NDK จะถือว่ามีการจัดแนวสแต็ก 16 ไบต์ก่อนการเรียกฟังก์ชัน เครื่องมือและตัวเลือกเริ่มต้นจะบังคับใช้กฎนี้ หากเขียนโค้ดแอสเซมบลี คุณต้องจัดระเบียบกองซ้อนให้ถูกต้อง และตรวจสอบว่าคอมไพเลอร์อื่นๆ ปฏิบัติตามกฎนี้ด้วย

    โปรดดูรายละเอียดเพิ่มเติมในเอกสารต่อไปนี้

    ABI นี้ใช้ long double 64 บิต (IEEE binary64 เหมือนกับ double ไม่ใช่ long double 80 บิตสำหรับ Intel เท่านั้นซึ่งเป็นที่นิยมมากกว่า)

    x86_64

    ABI นี้มีไว้สำหรับ CPU ที่รองรับชุดคำสั่งที่โดยทั่วไปเรียกว่า "x86-64"

    ABI ของ Android ประกอบด้วยชุดคำสั่งพื้นฐาน รวมถึง MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 และคำสั่ง POPCNT

    ABI จะไม่รวมชุดคำสั่ง x86-64 อื่นๆ ที่ไม่บังคับ เช่น MOVBE, SHA หรือ AVX รูปแบบต่างๆ คุณยังคงใช้ส่วนขยายเหล่านี้ได้ ตราบใดที่คุณใช้การสอดแนมฟีเจอร์รันไทม์เพื่อเปิดใช้ส่วนขยายเหล่านั้น และระบุทางเลือกสําหรับอุปกรณ์ที่ไม่รองรับ

    ดูรายละเอียดเพิ่มเติมได้ที่เอกสารต่อไปนี้

    ABI นี้ใช้ long double แบบ 128 บิต (ไบนารี IEEE128)

    สร้างโค้ดสําหรับ 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 คุณจะสร้างสำหรับ ABI รายการเดียวในแต่ละครั้งและต้องระบุ ABI อย่างชัดเจน คุณทําเช่นนี้ได้ด้วยตัวแปร ANDROID_ABI ซึ่งต้องระบุในบรรทัดคําสั่ง (ตั้งค่าใน CMakeLists.txt ไม่ได้) เช่น

    $ cmake -DANDROID_ABI=arm64-v8a ...
    $ cmake -DANDROID_ABI=armeabi-v7a ...
    $ cmake -DANDROID_ABI=x86 ...
    $ cmake -DANDROID_ABI=x86_64 ...
    

    สําหรับ Flag อื่นๆ ที่ต้องส่งไปยัง CMake เพื่อสร้างด้วย NDK โปรดดูคู่มือ CMake

    ลักษณะการทำงานเริ่มต้นของระบบบิลด์คือการรวมไบนารีสำหรับ ABI แต่ละรายการไว้ใน APK ไฟล์เดียว หรือที่เรียกว่า Fat APK APK แบบรวมมีขนาดที่ใหญ่กว่า APK ที่มีเฉพาะไบนารีสําหรับ ABI เดียวอย่างมาก ข้อเสียคือ APK จะใหญ่ขึ้น แต่ข้อดีคือจะใช้งานร่วมกับอุปกรณ์ได้มากขึ้น เราขอแนะนําอย่างยิ่งให้คุณใช้ประโยชน์จาก App Bundle หรือ APK Split เพื่อลดขนาด APK ของคุณในขณะที่ยังคงรักษาความเข้ากันได้กับอุปกรณ์สูงสุด

    เครื่องมือจัดการแพ็กเกจจะแตกไฟล์แพ็กเกจเฉพาะโค้ดเครื่องที่เหมาะสมที่สุดสำหรับอุปกรณ์เป้าหมายเท่านั้น ณ เวลาที่ติดตั้ง โปรดดูรายละเอียดที่หัวข้อการดึงข้อมูลโค้ดเนทีฟโดยอัตโนมัติเมื่อติดตั้ง

    การจัดการ ABI บนแพลตฟอร์ม Android

    ส่วนนี้จะให้รายละเอียดเกี่ยวกับวิธีที่แพลตฟอร์ม Android จัดการโค้ดเนทีฟใน APK

    โค้ดที่มาพร้อมเครื่องในแพ็กเกจแอป

    ทั้ง Play Store และ Package Manager คาดว่าจะพบไลบรารีที่ NDK สร้างขึ้นในเส้นทางไฟล์ภายใน APK ซึ่งตรงกับรูปแบบต่อไปนี้

    /lib/<abi>/lib<name>.so
    

    ในที่นี้ <abi> คือชื่อ ABI รายการใดรายการหนึ่งที่อยู่ในส่วน ABI ที่รองรับ และ <name> คือชื่อไลบรารีที่คุณกำหนดไว้สำหรับตัวแปร LOCAL_MODULE ในไฟล์ Android.mk เนื่องจากไฟล์ APK เป็นเพียงไฟล์ ZIP คุณจึงเปิดไฟล์และยืนยันว่าไลบรารีแบบเนทีฟที่แชร์อยู่ในตำแหน่งที่ถูกต้องได้ง่ายๆ

    หากไม่พบไลบรารีที่ใช้ร่วมกันแบบเนทีฟในตำแหน่งที่ควรจะอยู่ ระบบจะใช้ไลบรารีเหล่านั้นไม่ได้ ในกรณีเช่นนี้ แอปจะต้องคัดลอกไลบรารี แล้วดำเนินการ dlopen()

    ใน APK แบบรวม ไลบรารีแต่ละรายการจะอยู่ในไดเรกทอรีที่มีชื่อตรงกับ ABI ที่เกี่ยวข้อง ตัวอย่างเช่น APK แบบอ้วนอาจมีสิ่งต่อไปนี้

    /lib/armeabi/libfoo.so
    /lib/armeabi-v7a/libfoo.so
    /lib/arm64-v8a/libfoo.so
    /lib/x86/libfoo.so
    /lib/x86_64/libfoo.so
    

    หมายเหตุ: อุปกรณ์ Android ที่ใช้ ARMv7 ซึ่งใช้เวอร์ชัน 4.0.3 หรือเก่ากว่าติดตั้งไลบรารีแบบเนทีฟจากไดเรกทอรี armeabi แทนไดเรกทอรี armeabi-v7a หากมีทั้ง 2 ไดเรกทอรี เนื่องจาก /lib/armeabi/ อยู่หลัง /lib/armeabi-v7a/ ใน APK ปัญหานี้ได้รับการแก้ไขตั้งแต่เวอร์ชัน 4.0.4

    การรองรับ ABI ของแพลตฟอร์ม Android

    ระบบ Android จะทราบ ABI ที่รองรับขณะรันไทม์ เนื่องจากพร็อพเพอร์ตี้ของระบบสำหรับบิลด์ที่เฉพาะเจาะจงจะระบุข้อมูลต่อไปนี้

    • ABI หลักสำหรับอุปกรณ์ ซึ่งสอดคล้องกับรหัสเครื่องที่ใช้ในอิมเมจระบบ
    • ABI สำรองซึ่งสอดคล้องกับ ABI อื่นๆ ที่อิมเมจระบบรองรับด้วย (ไม่บังคับ)

    กลไกนี้ช่วยให้มั่นใจได้ว่าระบบจะดึงข้อมูลโค้ดเครื่องที่ดีที่สุดจากแพ็กเกจ ณ เวลาที่ติดตั้ง

    คุณควรคอมไพล์โดยตรงสำหรับ ABI หลักเพื่อให้ได้ประสิทธิภาพที่ดีที่สุด ตัวอย่างเช่น อุปกรณ์ที่ใช้ ARMv5TE ทั่วไปจะกำหนด ABI หลักเท่านั้น ซึ่งก็คือ armeabi ในทางตรงกันข้าม อุปกรณ์ที่ใช้ ARMv7 โดยทั่วไปจะกำหนด ABI หลักเป็น armeabi-v7a และ ABI รองเป็น armeabi เนื่องจากสามารถเรียกใช้ไบนารีเนทีฟของแอปพลิเคชันที่สร้างขึ้นสำหรับแต่ละ ABI ได้

    นอกจากนี้ อุปกรณ์ 64 บิตยังรองรับตัวแปร 32 บิตด้วย การใช้อุปกรณ์ arm64-v8a เป็นตัวอย่าง อุปกรณ์ดังกล่าวยังเรียกใช้โค้ด armeabi และ armeabi-v7a ได้ด้วย อย่างไรก็ตาม โปรดทราบว่าแอปพลิเคชันจะทำงานได้ดีขึ้นมากในอุปกรณ์ 64 บิตหากกำหนดเป้าหมายเป็น arm64-v8a แทนที่จะใช้อุปกรณ์ที่ทำงานบนแอปพลิเคชันเวอร์ชัน armeabi-v7a

    อุปกรณ์ที่ใช้ x86 จำนวนมากยังเรียกใช้ไบนารี NDK ของ armeabi-v7a และ armeabi ได้ด้วย สําหรับอุปกรณ์ดังกล่าว ABI หลักจะเป็น x86 และ ABI รองจะเป็น armeabi-v7a

    คุณสามารถบังคับติดตั้ง APK สำหรับ ABI ที่เฉพาะเจาะจงได้ ซึ่งมีประโยชน์สำหรับการทดสอบ ใช้คำสั่งต่อไปนี้

    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%

    ดูดูข้อมูลเกี่ยวกับสถาปัตยกรรม - การป้องกันซอฟต์แวร์ที่ซับซ้อน (PDF) ของ Arm เพื่อดูคำอธิบายโดยละเอียดเกี่ยวกับเวกเตอร์การโจมตีที่ PAC/BTI กำหนดเป้าหมาย และวิธีการทำงานของการป้องกัน

    การเปลี่ยนแปลงในบิลด์

    ndk-build

    ตั้งค่า LOCAL_BRANCH_PROTECTION := standard ในโมดูลแต่ละรายการของ Android.mk

    CMake

    ใช้ target_compile_options($TARGET PRIVATE -mbranch-protection=standard) สำหรับแต่ละเป้าหมายใน CMakeLists.txt

    ระบบบิลด์อื่นๆ

    คอมไพล์โค้ดโดยใช้ -mbranch-protection=standard Flag นี้ใช้ได้เมื่อคอมไพล์สำหรับ 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 เพื่อขอเวอร์ชันที่แก้ไขแล้ว