Android एबीआई

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

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

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

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

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

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

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
  • LAHF-SAHF
  • पूरा x86-64-v2.

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

    armeabi-v7a

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

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

    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-बिट ARM सीपीयू के लिए है.

    ABI के उन हिस्सों के बारे में पूरी जानकारी पाने के लिए, Arm की आर्किटेक्चर के बारे में जानें लेख पढ़ें जो Android के लिए खास तौर पर नहीं हैं. Arm, 64-बिट Android डेवलपमेंट में भी, ऐप्लिकेशन को पोर्ट करने के बारे में सलाह देता है.

    बेहतर SIMD एक्सटेंशन का फ़ायदा पाने के लिए, C और C++ कोड में Neon इंट्रिन्सिक का इस्तेमाल किया जा सकता है. Armv8-A के लिए Neon प्रोग्रामर गाइड में, 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 एक्सटेंशन शामिल होते हैं.

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

    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 निर्देश शामिल हैं.

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

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

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

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

    Gradle

    Gradle, डिफ़ॉल्ट रूप से उन सभी एबीआई के लिए बिल्ड करता है जिन्हें बंद नहीं किया गया है. भले ही, इसका इस्तेमाल 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 प्लैटफ़ॉर्म, APKs में मौजूद नेटिव कोड को कैसे मैनेज करता है.

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

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

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

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

    अगर सिस्टम को नेटिव शेयर की गई लाइब्रेरी वहां नहीं मिलतीं जहां उन्हें मिलना चाहिए, तो वह उनका इस्तेमाल नहीं कर सकता. ऐसे में, ऐप्लिकेशन को लाइब्रेरी को खुद कॉपी करना होगा और फिर 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-v7a डायरेक्ट्री के बजाय armeabi डायरेक्ट्री से इंस्टॉल होती हैं. ऐसा इसलिए होता है, क्योंकि APK में /lib/armeabi/, /lib/armeabi-v7a/ के बाद आता है. यह समस्या 4.0.4 से ठीक हो गई है.

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

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

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

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

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

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

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

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

    adb install --abi abi-identifier path_to_apk
    

    इंस्टॉल के समय, नेटिव कोड का अपने-आप निकाला जाना

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

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

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

    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 को चालू करने से, हमले के कुछ वैक्टर से सुरक्षा मिलेगी. पीएसी, फ़ंक्शन के प्रोलॉग में क्रिप्टोग्राफ़िक तरीके से साइन करके, रिटर्न पते की सुरक्षा करता है. साथ ही, यह भी जांच करता है कि रिटर्न पते पर अब भी सही तरीके से साइन किया गया है या नहीं. बीटीआई, आपके कोड में मनमुताबिक जगहों पर जाने से रोकता है. इसके लिए, यह ज़रूरी है कि हर शाखा का टारगेट एक खास निर्देश हो. यह निर्देश प्रोसेसर को सिर्फ़ यह बताता है कि वहां जाना ठीक है.

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

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

    हमले के वेक्टर PAC/BTI टारगेट और सुरक्षा के काम करने के तरीके के बारे में ज़्यादा जानने के लिए, Arm का आर्किटेक्चर के बारे में जानें - मुश्किल सॉफ़्टवेयर के लिए सुरक्षा उपलब्ध कराना (PDF) पढ़ें.

    बदलावों को बिल्ड करना

    ndk-build

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

    CMake

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

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

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

    समस्या का हल

    हमें PAC/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 के मौजूदा वर्शन पर अपग्रेड करें.

    • कुछ ऐप्लिकेशन डीआरएम सिस्टम के पुराने वर्शन, ऐसा कोड जनरेट करते हैं जो PAC/BTI की ज़रूरी शर्तों का उल्लंघन करता है. अगर ऐप्लिकेशन के लिए डीआरएम का इस्तेमाल किया जा रहा है और आपको PAC/BTI चालू करने में समस्याएं आ रही हैं, तो ठीक किए गए वर्शन के लिए अपने डीआरएम वेंडर से संपर्क करें.