अलग-अलग Android डिवाइसों में अलग-अलग सीपीयू इस्तेमाल किए जाते हैं. इस वजह से, ये अलग-अलग दिशा-निर्देशों के सेट के साथ काम करते हैं. सीपीयू और दिशा-निर्देशों के हर सेट के लिए, ऐप्लिकेशन बाइनरी इंटरफ़ेस (एबीआई) अलग होता है. एबीआई में यह जानकारी शामिल होती है:
- सीपीयू के दिशा-निर्देशों का वह सेट (और एक्सटेंशन) जिसका इस्तेमाल किया जा सकता है.
- रनटाइम के दौरान, मेमोरी स्टोर और लोड करने की एंडियननेस. Android हमेशा लिटिल-एंडियन होता है.
- ऐप्लिकेशन और सिस्टम के बीच डेटा पास करने के लिए, कन्वेंशन. इनमें अलाइनमेंट की पाबंदियां और सिस्टम, फ़ंक्शन कॉल करते समय स्टैक और रजिस्टर का इस्तेमाल कैसे करता है, जैसी जानकारी शामिल होती है.
- एक्ज़ीक्यूटेबल बाइनरी का फ़ॉर्मैट. जैसे, प्रोग्राम और शेयर की गई लाइब्रेरी. साथ ही, वे किस तरह के कॉन्टेंट के साथ काम करती हैं. Android हमेशा ELF का इस्तेमाल करता है. ज़्यादा जानकारी के लिए, ELF System V Application Binary Interface देखें.
- C++ के नामों को कैसे मैंगल किया जाता है. ज़्यादा जानकारी के लिए, Generic/Itanium C++ ABI देखें.
इस पेज पर, NDK के साथ काम करने वाले एबीआई की सूची दी गई है. साथ ही, यह भी बताया गया है कि हर एबीआई कैसे काम करता है.
एबीआई, प्लैटफ़ॉर्म के साथ काम करने वाले नेटिव एपीआई को भी कहा जा सकता है. 32-बिट वाले सिस्टम पर एबीआई से जुड़ी समस्याओं की सूची देखने के लिए, 32-बिट वाले एबीआई में मौजूद गड़बड़ियां देखें.
काम करने वाले एबीआई
टेबल 1. एबीआई और काम करने वाले दिशा-निर्देशों के सेट.
| एबीआई | काम करने वाले दिशा-निर्देशों के सेट | नोट |
|---|---|---|
armeabi-v7a |
|
ARMv5/v6 डिवाइसों के साथ काम नहीं करता. |
arm64-v8a |
सिर्फ़ Armv8.0. | |
x86 |
MOVBE या SSE4 के साथ काम नहीं करता. | |
x86_64 |
|
पूरा 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 सीपीयू के लिए है.
एबीआई के उन हिस्सों के बारे में पूरी जानकारी पाने के लिए जो Android के लिए खास नहीं हैं, Arm का 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-बाइट स्टैक अलाइनमेंट का इस्तेमाल करता है. डिफ़ॉल्ट टूल और विकल्प, इस नियम को लागू करते हैं. अगर असेंबली कोड लिखा जा रहा है, तो आपको स्टैक अलाइनमेंट बनाए रखना होगा. साथ ही, यह पक्का करना होगा कि अन्य कंपाइलर भी इस नियम का पालन करें.
ज़्यादा जानकारी के लिए, ये दस्तावेज़ देखें:
- अलग-अलग 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
यह एबीआई, 64-बिट long double (IEEE binary64, जो double के बराबर है. यह 80-बिट Intel-only long double नहीं है, जिसका इस्तेमाल आम तौर पर किया जाता है)
.
x86_64
यह एबीआई, उन सीपीयू के लिए है जो दिशा-निर्देशों के उस सेट के साथ काम करते हैं जिसे आम तौर पर "x86-64-v2" कहा जाता है.
Android के एबीआई में, बेस इंस्ट्रक्शन सेट के साथ-साथ MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, और POPCNT निर्देश शामिल हैं.
एबीआई में, 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
यह एबीआई, 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-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
Android प्लैटफ़ॉर्म पर एबीआई के साथ काम करने की सुविधा
Android सिस्टम को रनटाइम के दौरान पता होता है कि वह किन एबीआई के साथ काम करता है. ऐसा इसलिए, क्योंकि बिल्ड के लिए खास सिस्टम प्रॉपर्टी से यह पता चलता है:
- डिवाइस के लिए प्राइमरी एबीआई. यह सिस्टम इमेज में इस्तेमाल किए गए मशीन कोड से जुड़ा होता है.
- ज़रूरी नहीं, सेकंडरी एबीआई. यह उस एबीआई से जुड़ा होता है जिसके साथ सिस्टम इमेज भी काम करती है.
इस तरीके से यह पक्का किया जाता है कि सिस्टम, इंस्टॉल करते समय पैकेज से सबसे अच्छा मशीन कोड एक्सट्रैक्ट करे.
किसी खास एबीआई के लिए, 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% होती है.
अटैक वेक्टर PAC/BTI को टारगेट करने और सुरक्षा के काम करने के तरीके के बारे में ज़्यादा जानकारी पाने के लिए, Arm का Learn the architecture - Providing protection for complex software (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 एबीआई के लिए कंपाइल करते समय काम करता है. लिंक करते समय, इस फ़्लैग का इस्तेमाल करने की ज़रूरत नहीं है.
समस्या का हल
हमें 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 वेंडर से संपर्क करें.