Android एबीआई

अलग-अलग Android डिवाइसों में अलग-अलग सीपीयू इस्तेमाल किए जाते हैं. इस वजह से, ये अलग-अलग निर्देश सेट के साथ काम करते हैं. सीपीयू और निर्देश सेट के हर कॉम्बिनेशन का अपना ऐप्लिकेशन बाइनरी इंटरफ़ेस (एबीआई) होता है. एबीआई में यह जानकारी शामिल होती है:

  • सीपीयू के निर्देश सेट (और एक्सटेंशन) जिनका इस्तेमाल किया जा सकता है.
  • रनटाइम के दौरान, मेमोरी स्टोर और लोड करने की एंडियननेस. Android हमेशा लिटिल-एंडियन होता है.
  • ऐप्लिकेशन और सिस्टम के बीच डेटा पास करने के लिए, कन्वेंशन. इनमें अलाइनमेंट की पाबंदियां शामिल हैं. साथ ही, यह भी शामिल है कि फ़ंक्शन कॉल करते समय सिस्टम, स्टैक और रजिस्टर का इस्तेमाल कैसे करता है.
  • एक्ज़ीक्यूटेबल बाइनरी का फ़ॉर्मैट. जैसे, प्रोग्राम और शेयर की गई लाइब्रेरी. साथ ही, यह भी शामिल है कि ये किस तरह के कॉन्टेंट के साथ काम करते हैं. Android हमेशा ELF का इस्तेमाल करता है. ज़्यादा जानकारी के लिए, ELF System V Application Binary Interface देखें.
  • C++ के नामों को कैसे मैंगल किया जाता है. ज़्यादा जानकारी के लिए, Generic/Itanium C++ ABI देखें.

इस पेज पर, उन एबीआई की सूची दी गई है जो NDK के साथ काम करते हैं. साथ ही, यह भी बताया गया है कि हर एबीआई कैसे काम करता है.

एबीआई, प्लैटफ़ॉर्म के साथ काम करने वाले नेटिव एपीआई को भी कहा जा सकता है. 32-बिट वाले सिस्टम पर एबीआई से जुड़ी समस्याओं की सूची देखने के लिए, 32-बिट वाले एबीआई में मौजूद गड़बड़ियां देखें.

काम करने वाले एबीआई

टेबल 1. एबीआई और काम करने वाले निर्देश सेट.

एबीआई काम करने वाले निर्देश सेट नोट
armeabi-v7a
  • armeabi
  • Thumb-2
  • Neon
  • 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
  • LAHF-SAHF
  • पूरा x86-64-v2.

    ध्यान दें: पहले NDK, ARMv5 (armeabi) के साथ-साथ 32-बिट और 64-बिट MIPS के साथ काम करता था. हालांकि, NDK r17 में इन एबीआई के साथ काम करने की सुविधा हटा दी गई है.

    armeabi-v7a

    यह एबीआई, 32-बिट वाले एआरएम सीपीयू के लिए है. इसमें Thumb-2 और Neon शामिल हैं.

    एबीआई के उन हिस्सों के बारे में जानकारी पाने के लिए जो Android के लिए खास नहीं हैं, एआरएम आर्किटेक्चर के लिए ऐप्लिकेशन बाइनरी इंटरफ़ेस (एबीआई) देखें

    NDK के बिल्ड सिस्टम, डिफ़ॉल्ट रूप से Thumb-2 कोड जनरेट करते हैं. हालांकि, अगर ndk-build के लिए Android.mk में LOCAL_ARM_MODE या CMake को कॉन्फ़िगर करते समय ANDROID_ARM_MODE का इस्तेमाल किया जाता है, तो ऐसा नहीं होता.

    Neon के इतिहास के बारे में ज़्यादा जानने के लिए, Neon के साथ काम करने की सुविधा देखें.

    ऐतिहासिक वजहों से, यह एबीआई -mfloat-abi=softfp का इस्तेमाल करता है. इस वजह से, फ़ंक्शन कॉल करते समय, सभी float वैल्यू को इंटिजर रजिस्टर में और सभी double वैल्यू को इंटिजर रजिस्टर पेयर में पास किया जाता है. नाम के बावजूद, इसका असर सिर्फ़ फ़्लोटिंग पॉइंट कॉलिंग कन्वेंशन पर पड़ता है: कंपाइलर, अंकगणित के लिए हार्डवेयर फ़्लोटिंग पॉइंट निर्देशों का इस्तेमाल करता रहेगा.

    यह एबीआई, 64-बिट long double (IEEE binary64, जो double के बराबर है) का इस्तेमाल करता है.

    arm64-v8a

    यह एबीआई, 64-बिट वाले एआरएम सीपीयू के लिए है.

    एबीआई के उन हिस्सों के बारे में पूरी जानकारी पाने के लिए जो Android के लिए खास नहीं हैं, Arm's Learn the Architecture देखें. Arm , 64-बिट वाले Android डेवलपमेंट में पोर्टिंग के बारे में कुछ सलाह भी देता है.

    एडवांस SIMD एक्सटेंशन का फ़ायदा पाने के लिए, C और C++ कोड में Neon इंट्रिंसिक का इस्तेमाल किया जा सकता है. Armv8-A के लिए Neon Programmer's Guide में, Neon इंट्रिंसिक और सामान्य तौर पर Neon प्रोग्रामिंग के बारे में ज़्यादा जानकारी दी गई है.

    Android पर, प्लैटफ़ॉर्म के लिए खास x18 रजिस्टर, ShadowCallStack के लिए रिज़र्व होता है. आपके कोड को इसे नहीं छूना चाहिए. Clang के मौजूदा वर्शन, Android पर डिफ़ॉल्ट रूप से -ffixed-x18 विकल्प का इस्तेमाल करते हैं. इसलिए, अगर आपने असेंबलर को खुद से नहीं लिखा है (या बहुत पुराना कंपाइलर इस्तेमाल नहीं किया है), तो आपको इसके बारे में चिंता करने की ज़रूरत नहीं है.

    यह एबीआई, 128-बिट long double (IEEE binary128) का इस्तेमाल करता है.

    x86

    यह एबीआई, उन सीपीयू के लिए है जो "x86", "i386" या "IA-32" के तौर पर जाने जाने वाले निर्देश सेट के साथ काम करते हैं.

    Android के एबीआई में, बेस निर्देश सेट के साथ-साथ MMX, SSE, SSE2, SSE3, और SSSE3 एक्सटेंशन शामिल हैं.

    एबीआई में, IA-32 निर्देश सेट के अन्य वैकल्पिक एक्सटेंशन शामिल नहीं हैं. जैसे, MOVBE या SSE4 का कोई भी वैरिएंट. इन एक्सटेंशन का इस्तेमाल किया जा सकता है. हालांकि, इसके लिए आपको रनटाइम फ़ीचर-प्रोबिंग का इस्तेमाल करके इन्हें चालू करना होगा. साथ ही, उन डिवाइसों के लिए फ़ॉलबैक उपलब्ध कराने होंगे जो इनके साथ काम नहीं करते.

    NDK टूलचेन, फ़ंक्शन कॉल से पहले 16-बाइट स्टैक अलाइनमेंट का इस्तेमाल करता है. डिफ़ॉल्ट टूल और विकल्प, इस नियम को लागू करते हैं. अगर असेंबली कोड लिखा जा रहा है, तो आपको स्टैक अलाइनमेंट बनाए रखना होगा. साथ ही, यह भी पक्का करना होगा कि अन्य कंपाइलर भी इस नियम का पालन करें.

    ज़्यादा जानकारी के लिए, ये दस्तावेज़ देखें:

    यह एबीआई, 64-बिट long double (IEEE binary64, जो double के बराबर है. यह ज़्यादातर इस्तेमाल होने वाला 80-बिट Intel-only long double नहीं है) का इस्तेमाल करता है.

    x86_64

    यह एबीआई, उन सीपीयू के लिए है जो "x86-64" के तौर पर जाने जाने वाले निर्देश सेट के साथ काम करते हैं.

    Android के एबीआई में, बेस निर्देश सेट के साथ-साथ MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, और POPCNT निर्देश शामिल हैं.

    एबीआई में, x86-64 निर्देश सेट के अन्य वैकल्पिक एक्सटेंशन शामिल नहीं हैं. जैसे, MOVBE, SHA या AVX का कोई भी वैरिएंट. इन एक्सटेंशन का इस्तेमाल किया जा सकता है. हालांकि, इसके लिए आपको रनटाइम फ़ीचर-प्रोबिंग का इस्तेमाल करके इन्हें चालू करना होगा. साथ ही, उन डिवाइसों के लिए फ़ॉलबैक उपलब्ध कराने होंगे जो इनके साथ काम नहीं करते.

    ज़्यादा जानकारी के लिए, ये दस्तावेज़ देखें:

    यह एबीआई, 128-बिट long double (IEEE binary128) का इस्तेमाल करता है.

    किसी खास एबीआई के लिए कोड जनरेट करना

    ग्रेडल

    ग्रेडल (चाहे Android Studio के ज़रिए इस्तेमाल किया जाए या कमांड लाइन से), डिफ़ॉल्ट रूप से बंद किए गए सभी एबीआई के लिए बिल्ड करता है. अपने ऐप्लिकेशन के साथ काम करने वाले एबीआई के सेट को सीमित करने के लिए, abiFilters का इस्तेमाल करें. उदाहरण के लिए, सिर्फ़ 64-बिट वाले एबीआई के लिए बिल्ड करने के लिए, अपने build.gradle में यह कॉन्फ़िगरेशन सेट करें:

    android {
        defaultConfig {
            ndk {
                abiFilters 'arm64-v8a', 'x86_64'
            }
        }
    }
    

    ndk-build

    ndk-build, डिफ़ॉल्ट रूप से बंद किए गए सभी एबीआई के लिए बिल्ड करता है. Application.mk फ़ाइल में APP_ABI सेट करके, किसी खास एबीआई को टारगेट किया जा सकता है. यहां दिए गए स्निपेट में, 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 की मदद से, एक बार में सिर्फ़ एक एबीआई के लिए बिल्ड किया जा सकता है. इसके लिए, आपको अपना एबीआई साफ़ तौर पर तय करना होगा. इसके लिए, ANDROID_ABI वैरिएबल का इस्तेमाल करें. इसे कमांड लाइन पर तय करना होगा. इसे CMakeLists.txt में सेट नहीं किया जा सकता. उदाहरण के लिए:

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

    NDK की मदद से बिल्ड करने के लिए, CMake को पास किए जाने वाले अन्य फ़्लैग के बारे में जानने के लिए, CMake की गाइड देखें.

    बिल्ड सिस्टम का डिफ़ॉल्ट तरीका यह है कि हर एबीआई के लिए बाइनरी को एक ही APK में शामिल किया जाए. इसे फ़ैट APK भी कहा जाता है. फ़ैट APK, सिर्फ़ एक एबीआई के लिए बाइनरी वाले APK से काफ़ी बड़ा होता है. इसका फ़ायदा यह है कि यह ज़्यादा डिवाइसों के साथ काम करता है. हालांकि, इसकी वजह से APK का साइज़ बढ़ जाता है. हमारा सुझाव है कि APK के साइज़ को कम करने के लिए, ऐप्लिकेशन बंडल या APK स्प्लिट का इस्तेमाल करें. इससे डिवाइसों के साथ ज़्यादा से ज़्यादा कंपैटिबिलिटी बनाए रखी जा सकती है.

    इंस्टॉल करते समय, पैकेज मैनेजर सिर्फ़ टारगेट डिवाइस के लिए सबसे सही मशीन कोड को अनपैक करता है. ज़्यादा जानकारी के लिए, इंस्टॉल करते समय नेटिव कोड को अपने-आप एक्सट्रैक्ट करना देखें.

    Android प्लैटफ़ॉर्म पर एबीआई मैनेजमेंट

    इस सेक्शन में, इस बारे में जानकारी दी गई है कि Android प्लैटफ़ॉर्म, APK में नेटिव कोड को कैसे मैनेज करता है.

    ऐप्लिकेशन पैकेज में नेटिव कोड

    Play Store और Package Manager, दोनों को APK में फ़ाइल पाथ पर NDK से जनरेट की गई लाइब्रेरी मिलने की उम्मीद होती है. ये फ़ाइल पाथ, इस पैटर्न के मुताबिक होने चाहिए:

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

    यहां, <abi> काम करने वाले एबीआई में शामिल एबीआई के नामों में से एक है. वहीं, <name> लाइब्रेरी का वह नाम है जिसे आपने LOCAL_MODULE वैरिएबल के लिए Android.mk फ़ाइल में तय किया है. APK फ़ाइलें, zip फ़ाइलें होती हैं. इसलिए, इन्हें खोलना और यह पुष्टि करना आसान है कि शेयर की गई नेटिव लाइब्रेरी, सही जगह पर हैं.

    अगर सिस्टम को शेयर की गई नेटिव लाइब्रेरी, सही जगह पर नहीं मिलती हैं, तो वह उनका इस्तेमाल नहीं कर सकता. ऐसे में, ऐप्लिकेशन को लाइब्रेरी को कॉपी करना होगा. इसके बाद, dlopen() करना होगा.

    फ़ैट APK में, हर लाइब्रेरी एक डायरेक्ट्री में मौजूद होती है. इस डायरेक्ट्री का नाम, उससे जुड़े एबीआई के नाम से मेल खाता है. उदाहरण के लिए, फ़ैट APK में यह शामिल हो सकता है:

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

    ध्यान दें: ARMv7 पर आधारित Android डिवाइसों पर, 4.0.3 या इससे पहले के वर्शन इस्तेमाल करने पर, नेटिव लाइब्रेरी को armeabi डायरेक्ट्री से इंस्टॉल किया जाता है. ऐसा तब होता है, जब armeabi-v7a और दोनों डायरेक्ट्री मौजूद हों. ऐसा इसलिए होता है, क्योंकि APK में /lib/armeabi/, /lib/armeabi-v7a/ के बाद आता है. इस समस्या को 4.0.4 से ठीक कर दिया गया है.

    Android प्लैटफ़ॉर्म पर एबीआई के साथ काम करने की सुविधा

    Android सिस्टम को रनटाइम के दौरान पता होता है कि वह किन एबीआई के साथ काम करता है. ऐसा इसलिए, क्योंकि बिल्ड के लिए खास सिस्टम प्रॉपर्टी से यह पता चलता है कि:

    • डिवाइस के लिए प्राइमरी एबीआई. यह सिस्टम इमेज में इस्तेमाल किए गए मशीन कोड से जुड़ा होता है.
    • ज़रूरी नहीं है कि सेकंडरी एबीआई. यह उस एबीआई से जुड़ा होता है जिसके साथ सिस्टम इमेज भी काम करती है.

    इस मैकेनिज़्म से यह पक्का होता है कि सिस्टम, इंस्टॉल करते समय पैकेज से सबसे अच्छा मशीन कोड एक्सट्रैक्ट करे.

    बेहतरीन परफ़ॉर्मेंस के लिए, प्राइमरी एबीआई के लिए सीधे कंपाइल करना चाहिए. उदाहरण के लिए, ARMv5TE पर आधारित किसी सामान्य डिवाइस में, सिर्फ़ प्राइमरी एबीआई तय किया जाएगा: armeabi. इसके उलट, ARMv7 पर आधारित किसी सामान्य डिवाइस में, प्राइमरी एबीआई को armeabi-v7a और सेकंडरी एबीआई को armeabi के तौर पर तय किया जाएगा. ऐसा इसलिए, क्योंकि यह इन दोनों के लिए जनरेट की गई ऐप्लिकेशन नेटिव बाइनरी को चला सकता है.

    64-बिट वाले डिवाइस, अपने 32-बिट वाले वैरिएंट के साथ भी काम करते हैं. उदाहरण के लिए, arm64-v8a डिवाइस, armeabi और armeabi-v7a कोड भी चला सकता है. हालांकि, ध्यान दें कि अगर आपका ऐप्लिकेशन, arm64-v8a को टारगेट करता है, तो वह 64-बिट वाले डिवाइसों पर बेहतर परफ़ॉर्म करेगा. ऐसा तब होगा, जब वह आपके ऐप्लिकेशन के armeabi-v7a वर्शन को चलाने के लिए डिवाइस पर निर्भर नहीं होगा.

    x86 पर आधारित कई डिवाइस, armeabi-v7a और armeabi NDK बाइनरी भी चला सकते हैं. ऐसे डिवाइसों के लिए, प्राइमरी एबीआई x86 और सेकंडरी एबीआई armeabi-v7a होगा.

    किसी खास एबीआई के लिए, apk को ज़बरदस्ती इंस्टॉल किया जा सकता है. यह टेस्टिंग के लिए काम का है. यह कमांड इस्तेमाल करें:

    adb install --abi abi-identifier path_to_apk
    

    इंस्टॉल करते समय नेटिव कोड को अपने-आप एक्सट्रैक्ट करना

    किसी ऐप्लिकेशन को इंस्टॉल करते समय, पैकेज मैनेजर सेवा, APK को स्कैन करती है. साथ ही, इस फ़ॉर्मैट में शेयर की गई लाइब्रेरी ढूंढती है:

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

    अगर कोई लाइब्रेरी नहीं मिलती है और आपने सेकंडरी एबीआई तय किया है, तो सेवा, इस फ़ॉर्मैट में शेयर की गई लाइब्रेरी को स्कैन करती है:

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

    जब उसे लाइब्रेरी मिल जाती हैं, तो पैकेज मैनेजर उन्हें ऐप्लिकेशन की नेटिव लाइब्रेरी डायरेक्ट्री (<nativeLibraryDir>/) में, /lib/lib<name>.so पर कॉपी कर देता है. यहां दिए गए स्निपेट, 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: C/C++ के लिए PAC और BTI चालू करना

    PAC/BTI को चालू करने से, कुछ अटैक वेक्टर से सुरक्षा मिलेगी. PAC, रिटर्न अड्रेस को क्रिप्टोग्राफ़िक तरीके से साइन करके सुरक्षित करता है. इसके लिए, वह फ़ंक्शन के प्रोलॉग में रिटर्न अड्रेस को साइन करता है और एपिलॉग में यह जांचता है कि रिटर्न अड्रेस अब भी सही तरीके से साइन किया गया है या नहीं. BTI, आपके कोड में किसी भी जगह पर जंप करने से रोकता है. इसके लिए, यह ज़रूरी है कि हर ब्रांच टारगेट एक खास निर्देश हो. यह निर्देश, प्रोसेसर को सिर्फ़ यह बताता है कि वहां लैंड करना ठीक है.

    Android, PAC/BTI निर्देशों का इस्तेमाल करता है. ये निर्देश, पुराने प्रोसेसर पर कोई काम नहीं करते, क्योंकि वे नए निर्देशों के साथ काम नहीं करते. PAC/BTI से सुरक्षा सिर्फ़ ARMv9 डिवाइसों को मिलेगी. हालांकि, ARMv8 डिवाइसों पर भी वही कोड चलाया जा सकता है. इसके लिए, अपनी लाइब्रेरी के कई वैरिएंट की ज़रूरत नहीं है. ARMv9 डिवाइसों पर भी, PAC/BTI सिर्फ़ 64-बिट वाले कोड पर लागू होता है.

    PAC/BTI को चालू करने से, कोड के साइज़ में थोड़ी बढ़ोतरी होगी. आम तौर पर, यह 1% होती है.

    अटैक वेक्टर के बारे में ज़्यादा जानने के लिए, Arm's Learn the architecture - Providing protection for complex software (PDF) देखें. इसमें यह भी बताया गया है कि PAC/BTI, टारगेट और सुरक्षा कैसे काम करती है.

    बिल्ड में किए गए बदलाव

    ndk-build

    अपने Android.mk के हर मॉड्यूल में, LOCAL_BRANCH_PROTECTION := standard सेट करें.

    CMake

    अपने CMakeLists.txt में हर टारगेट के लिए, target_compile_options($TARGET PRIVATE -mbranch-protection=standard) का इस्तेमाल करें.

    अन्य बिल्ड सिस्टम

    अपने कोड को -mbranch-protection=standard का इस्तेमाल करके कंपाइल करें. यह फ़्लैग, सिर्फ़ arm64-v8a एबीआई के लिए कंपाइल करते समय काम करता है. लिंक करते समय, इस फ़्लैग का इस्तेमाल करने की ज़रूरत नहीं है.

    समस्या का हल

    हमें PAC/BTI के लिए कंपाइलर की सुविधा से जुड़ी किसी समस्या के बारे में जानकारी नहीं है. हालांकि:

    • लिंक करते समय, BTI और नॉन-बीटीआई कोड को मिक्स न करें. ऐसा करने से, ऐसी लाइब्रेरी बनती है जिसमें BTI सुरक्षा चालू नहीं होती. यह देखने के लिए कि आपकी लाइब्रेरी में BTI नोट है या नहीं, llvm-readelf का इस्तेमाल किया जा सकता है.
    $ 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 वेंडर से संपर्क करें.