تستخدم أجهزة Android المختلفة وحدات معالجة مركزية مختلفة، ما يؤدي بدوره إلى توفير مجموعات تعليمات مختلفة. تتسم كل مجموعة من مجموعات وحدة المعالجة المركزية ومجموعة التعليمات بواجهة التطبيق الثنائية (ABI) الخاصة بها. تتضمّن واجهة التطبيق الثنائية المعلومات التالية:
- مجموعة تعليمات وحدة المعالجة المركزية (والإضافات) التي يمكن استخدامها
- ترتيب البايتات في عمليات تخزين البيانات في الذاكرة وتحميلها في وقت التشغيل يستخدم Android دائمًا ترتيب البايتات الصغير.
- اتفاقيات تمرير البيانات بين التطبيقات والنظام، بما في ذلك قيود المحاذاة وكيفية استخدام النظام للمكدّس والسجلات عند استدعاء الدوال
- تنسيق الملفات الثنائية القابلة للتنفيذ، مثل البرامج والمكتبات المشترَكة، وأنواع المحتوى التي تتيحها يستخدم Android دائمًا تنسيق ELF. لمزيد من المعلومات، يُرجى الاطّلاع على ELF System V واجهة التطبيق الثنائية.
- كيفية تشويه أسماء C++ لمزيد من المعلومات، يُرجى الاطّلاع على مقالة Generic/Itanium C++ ABI.
تُدرِج هذه الصفحة واجهات التطبيق الثنائية التي تتيحها حزمة تطوير البرامج الأصلية (NDK)، وتوفّر معلومات حول كيفية عمل كل واجهة.
يمكن أن تشير واجهة التطبيق الثنائية أيضًا إلى واجهة برمجة التطبيقات الأصلية التي يتيحها النظام الأساسي. للاطّلاع على قائمة بأنواع مشاكل واجهة التطبيق الثنائية التي تؤثر في الأنظمة التي تعمل بنظام 32 بت، يُرجى الاطّلاع على مقالة أخطاء واجهة التطبيق الثنائية لنظام 32 بت.
واجهات التطبيق الثنائية المتاحة
الجدول 1: واجهات التطبيق الثنائية ومجموعات التعليمات المتاحة
| ABI | مجموعات التعليمات المتاحة | ملاحظات |
|---|---|---|
armeabi-v7a |
|
غير متوافقة مع أجهزة ARMv5/v6 |
arm64-v8a |
الإصدار Armv8.0 فقط | |
x86 |
لا تتوافق مع MOVBE أو SSE4 | |
x86_64 |
|
الإصدار x86-64-v2 الكامل. |
ملاحظة: في السابق، كانت حزمة تطوير البرامج الأصلية (NDK) تتيح ARMv5 (armeabi) وMIPS لنظامَي 32 بت و64 بت، ولكن تمت إزالة إمكانية استخدام واجهات التطبيق الثنائية هذه في الإصدار r17 من حزمة تطوير البرامج الأصلية (NDK).
armeabi-v7a
تُستخدَم واجهة التطبيق الثنائية هذه لوحدات المعالجة المركزية ARM لنظام 32 بت. وتتضمّن Thumb-2 ونيون.
للحصول على معلومات حول أجزاء واجهة التطبيق الثنائية التي لا تخص Android، يُرجى الاطّلاع على مقالة Application Binary Interface (ABI) for the ARM Architecture
تنشئ أنظمة الإصدار في حزمة تطوير البرامج الأصلية (NDK) رمز Thumb-2 تلقائيًا ما لم تستخدِم
LOCAL_ARM_MODE في ملف Android.mk لـ
ndk-build أو ANDROID_ARM_MODE عند إعداد CMake.
لمزيد من المعلومات حول سجلّ نيون، يُرجى الاطّلاع على مقالة Neon Support.
لأسباب تاريخية، تستخدِم واجهة التطبيق الثنائية هذه -mfloat-abi=softfp، ما يؤدي إلى تمرير جميع قيم float
في سجلات الأعداد الصحيحة وجميع قيم double في أزواج سجلات الأعداد الصحيحة عند إجراء استدعاءات الدوال. على الرغم من الاسم، لا يؤثر ذلك
إلا في اتفاقية الاستدعاء للنقطة العائمة: سيظل المحول البرمجي يستخدم
تعليمات النقطة العائمة للأجهزة في العمليات الحسابية.
تستخدِم واجهة التطبيق الثنائية هذه نظام 64 بت long double (IEEE binary64، وهي نفسها double).
arm64-v8a
تُستخدَم واجهة التطبيق الثنائية هذه لوحدات المعالجة المركزية ARM لنظام 64 بت.
يُرجى الاطّلاع على مقالة Arm's Learn the Architecture للحصول على تفاصيل كاملة حول أجزاء واجهة التطبيق الثنائية التي لا تخص Android. تقدّم Arm أيضًا بعض النصائح بشأن النقل في 64-bit Android Development.
يمكنك استخدام Neon intrinsics في رمز C وC++ للاستفادة من إضافة Advanced SIMD. يوفر دليل مبرمج نيون لـ Armv8-A مزيدًا من المعلومات حول Neon intrinsics وبرمجة نيون بشكل عام.
على Android، يكون سجل x18 الخاص بالنظام الأساسي محجوزًا لـ
ShadowCallStack
ويجب ألا يمسّه الرمز البرمجي. تستخدم الإصدارات الحالية من Clang تلقائيًا الخيار -ffixed-x18 على Android، لذا لن تحتاج إلى القلق بشأن ذلك ما لم تكن قد كتبت برنامجًا تجميعيًا يدويًا (أو كنت تستخدم محولًا برمجيًا قديمًا جدًا).
تستخدِم واجهة التطبيق الثنائية هذه لنظام 128 بت long double (IEEE binary128).
x86
تُستخدَم واجهة التطبيق الثنائية هذه لوحدات المعالجة المركزية التي تتيح مجموعة التعليمات المعروفة باسم "x86" أو "i386" أو "IA-32".
تتضمّن واجهة التطبيق الثنائية (ABI) في Android مجموعة التعليمات الأساسية بالإضافة إلى إضافات MMX و SSE و SSE2 و SSE3 و SSSE3.
لا تتضمّن واجهة التطبيق الثنائية أي إضافات أخرى اختيارية لمجموعة تعليمات IA-32، مثل MOVBE أو أي نوع من أنواع SSE4. يمكنك مواصلة استخدام هذه الإضافات طالما أنّك تستخدم ميزة التحقق من الميزات في وقت التشغيل لتفعيلها، وتوفّر حلولًا احتياطية للأجهزة التي لا تتيحها.
يفترض المحول البرمجي في حزمة تطوير البرامج الأصلية (NDK) محاذاة المكدّس بمقدار 16 بايت قبل استدعاء الدالة. تفرض الأدوات والخيارات التلقائية هذه القاعدة. إذا كنت تكتب رمزًا تجميعيًا، عليك التأكّد من الحفاظ على محاذاة المكدّس والتأكّد من أنّ المحولات البرمجية الأخرى تلتزم أيضًا بهذه القاعدة.
يُرجى الرجوع إلى المستندات التالية لمزيد من التفاصيل:
- Calling conventions for different C++ compilers and operating systems
- 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
تستخدِم واجهة التطبيق الثنائية هذه long double لنظام 64 بت (IEEE binary64، وهي نفسها double، وليست long double لنظام 80 بت من Intel فقط الأكثر
شيوعًا).
x86_64
تُستخدَم واجهة التطبيق الثنائية هذه لوحدات المعالجة المركزية التي تتيح مجموعة التعليمات المعروفة باسم "x86-64-v2".
تتضمّن واجهة التطبيق الثنائية في Android مجموعة التعليمات الأساسية بالإضافة إلى MMX، SSE، SSE2، SSE3، SSSE3، SSE4.1، SSE4.2، و تعليمات POPCNT.
لا تتضمّن واجهة التطبيق الثنائية أي إضافات أخرى اختيارية لمجموعة تعليمات x86-64، مثل MOVBE أو SHA أو أي نوع من أنواع AVX. يمكنك مواصلة استخدام هذه الإضافات طالما أنّك تستخدم ميزة التحقق من الميزات في وقت التشغيل لتفعيلها، وتوفّر حلولًا احتياطية للأجهزة التي لا تتيحها.
يُرجى الرجوع إلى المستندات التالية لمزيد من التفاصيل:
- Calling conventions for different C++ compilers and operating systems
- 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).
إنشاء رمز لواجهة تطبيق ثنائية معيّنة
Gradle
ينشئ Gradle (سواء تم استخدامه من خلال "استوديو Android" أو من سطر الأوامر) إصدارات لجميع واجهات التطبيق الثنائية غير المتوقفة تلقائيًا. لتقييد مجموعة واجهات التطبيق الثنائية التي يتيحها تطبيقك، استخدِم abiFilters. على سبيل المثال، لإنشاء إصدارات لواجهات التطبيق الثنائية لنظام 64 بت فقط، اضبط الإعدادات التالية في ملف build.gradle:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-build
ينشئ ndk-build إصدارات لجميع واجهات التطبيق الثنائية غير المتوقفة تلقائيًا. يمكنك استهداف واجهات تطبيق ثنائية معيّنة من خلال ضبط 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
باستخدام CMake، يمكنك إنشاء إصدار لواجهة تطبيق ثنائية واحدة في كل مرة ويجب تحديد واجهة التطبيق الثنائية بشكلٍ صريح. يمكنك إجراء ذلك باستخدام المتغيّر ANDROID_ABI، الذي يجب تحديده في سطر الأوامر (لا يمكن ضبطه في ملف CMakeLists.txt). على سبيل المثال:
$ cmake -DANDROID_ABI=arm64-v8a ...
$ cmake -DANDROID_ABI=armeabi-v7a ...
$ cmake -DANDROID_ABI=x86 ...
$ cmake -DANDROID_ABI=x86_64 ...
للتعرّف على العلامات الأخرى التي يجب تمريرها إلى CMake لإنشاء إصدار باستخدام حزمة تطوير البرامج الأصلية (NDK)، يُرجى الاطّلاع على دليل CMake.
إنّ السلوك التلقائي لنظام التصميم هو تضمين البرامج الثنائية لكل واجهة التطبيق الثنائية (ABI) في ملف APK واحد، يُعرف أيضًا باسم ملف APK كبير. يكون ملف APK الكبير أكبر بكثير من الملف الذي يحتوي على الملفات الثنائية لواجهة تطبيق ثنائية واحدة فقط، ولكنّ الميزة هي الحصول على توافق أوسع، ولكن على حساب ملف APK أكبر. ننصحك بشدة بالاستفادة من حِزم التطبيقات أو تقسيمات ملفات APK لتقليل حجم ملفات APK مع الحفاظ على الحد الأقصى من التوافق مع الأجهزة.
أثناء التثبيت، لا يفك مدير الحِزم سوى رمز الآلة الأنسب للجهاز المستهدَف. لمعرفة التفاصيل، يُرجى الاطّلاع على مقالة Automatic extraction of native code at install time.
إدارة واجهة التطبيق الثنائية على نظام Android الأساسي
يوفّر هذا القسم تفاصيل حول كيفية إدارة نظام Android الأساسي للرمز الأصلي في ملفات APK.
الرموز البرمجية الأصلية في حِزم التطبيقات
يتوقّع كلٌّ من "متجر Play" و"مدير الحِزم" العثور على المكتبات التي تم إنشاؤها باستخدام حزمة تطوير البرامج الأصلية (NDK) في مسارات الملفات داخل ملف APK التي تتطابق مع النمط التالي:
/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 لواجهة تطبيق ثنائية معيّنة ABI. قد يكون ذلك مفيدًا للاختبار على الأجهزة التي تتيح أكثر من واجهة تطبيق ثنائية واحدة. استخدِم الأمر التالي:
adb install --abi abi-identifier path_to_apk
الاستخراج التلقائي للرموز البرمجية الأصلية في وقت التثبيت
عند تثبيت تطبيق، تفحص خدمة أداة إدارة الحِزم ملف APK وتبحث عن أي مكتبات مشترَكة بالتنسيق التالي:
lib/<primary-abi>/lib<name>.so
إذا لم يتم العثور على أي مكتبة، وكنت قد حدّدت واجهة تطبيق ثنائية ثانوية، تبحث الخدمة عن مكتبات مشترَكة بالتنسيق التالي:
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 التي لا تفعل شيئًا على المعالِجات القديمة التي لا تتيح التعليمات الجديدة. لن تتوفّر حماية 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
اضبط LOCAL_BRANCH_PROTECTION := standard في كل وحدة من وحدات ملف Android.mk.
CMake
استخدِم target_compile_options($TARGET PRIVATE -mbranch-protection=standard) لكل هدف في ملف CMakeLists.txt.
أنظمة الإصدار الأخرى
يمكنك تجميع الرمز البرمجي باستخدام -mbranch-protection=standard. لا تعمل هذه العلامة إلا عند التجميع لواجهة التطبيق الثنائية 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) للحصول على إصدار صالح.