Android एबीआई

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

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

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

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

इस्तेमाल किए जा सकने वाले एबीआई

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

ABI इस्तेमाल किए जा सकने वाले निर्देशों के सेट नोट
armeabi-v7a
  • armeabi
  • थंब-2
  • नियॉन
  • यह ARMv5/v6 डिवाइसों के साथ काम नहीं करता.
    arm64-v8a
  • AArch64
  • सिर्फ़ Armv8.0.
    x86
  • x86 (IA-32)
  • एमएमएक्स
  • एसएसई/2/3
  • SSSE3
  • MOVBE या SSE4 के साथ काम नहीं करता.
    x86_64
  • x86-64
  • MMX
  • एसएसई/2/3
  • SSSE3
  • SSE4.1, 4.2
  • पीओपीसीएनटी
  • CMPXCHG16B
  • पूरा x86-64-v1, लेकिन x86-64-v2 का पूरा हिस्सा ही लें (LAHF-SAHF नहीं).

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

    armeabi-v7a

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

    एबीआई के उन हिस्सों के बारे में जानकारी पाने के लिए जो 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 इंट्रिन्सिक का इस्तेमाल किया जा सकता है. Neon Programmers Guide for Armv8-A में नियॉन इंट्रिंसिक्स और नियॉन प्रोग्रामिंग के बारे में ज़्यादा जानकारी दी गई है.

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

    एबीआई में, 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 की मदद से, एक बार में सिर्फ़ एक एबीआई के लिए बिल्ड किया जा सकता है. साथ ही, आपको अपने एबीआई के बारे में साफ़ तौर पर बताना होगा. ऐसा करने के लिए, 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
    

    ध्यान दें: 4.0.3 या इससे पहले के वर्शन वाले ARMv7 पर आधारित Android डिवाइसों में, दोनों डायरेक्ट्री मौजूद होने पर, 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
    

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

    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 सेट करें.

    सीमेक

    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 से पहले के) में, मैन्युअल तरीके से लिखे गए असेंबलर में एक गड़बड़ी है. इस वजह से, पीएससी काम नहीं करता. OpenSSL के मौजूदा वर्शन पर अपग्रेड करें.

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