تستخدم أجهزة Android المختلفة وحدات معالجة مركزية مختلفة، والتي بدورها تتيح استخدام مجموعات تعليمات مختلفة. تتسم كل مجموعة من مجموعات وحدة المعالجة المركزية ومجموعة التعليمات بواجهة تطبيق ثنائية (ABI) خاصة بها. تتضمّن واجهة التطبيق الثنائية المعلومات التالية:
- مجموعة تعليمات وحدة المعالجة المركزية (والإضافات) التي يمكن استخدامها
- ترتيب البايتات في عمليات تخزين البيانات في الذاكرة وتحميلها في وقت التشغيل يستخدم Android دائمًا ترتيب البايتات الصغير.
- الاتفاقيات المتعلّقة بنقل البيانات بين التطبيقات والنظام، بما في ذلك قيود المحاذاة، وكيفية استخدام النظام للمكدس والسجلات عند استدعاء الدوال
- تنسيق الملفات الثنائية القابلة للتنفيذ، مثل البرامج والمكتبات المشتركة، وأنواع المحتوى التي تتيحها يستخدم Android دائمًا تنسيق ELF. لمزيد من المعلومات، يُرجى الاطّلاع على ELF System V واجهة التطبيق الثنائية.
- كيفية تشويه أسماء 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) و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 بت.
يُرجى الاطّلاع على Learn the Architecture من Arm للحصول على تفاصيل كاملة حول أجزاء واجهة التطبيق الثنائية التي لا تخص 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".
تتضمّن واجهة التطبيق الثنائية في 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/libfoo.so /lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
ملاحظة: إذا كانت أجهزة Android المستندة إلى ARMv7 والتي تعمل بالإصدار 4.0.3 أو إصدار أقدم
مثبّتة، فإنّها تثبّت المكتبات الأصلية من دليل armeabi بدلاً من دليل armeabi-v7a
إذا كان كلا الدليلَين موجودًا. يرجع ذلك إلى أنّ /lib/armeabi/ يأتي بعد /lib/armeabi-v7a/ في ملف APK. تم حلّ هذه المشكلة بدءًا من الإصدار 4.0.4.
توافق نظام Android الأساسي مع واجهة التطبيق الثنائية
يعرف نظام Android في وقت التشغيل واجهات التطبيق الثنائية التي يتيحها، لأنّ خصائص النظام الخاصة بالإصدار تشير إلى ما يلي:
- واجهة التطبيق الثنائية الأساسية للجهاز، والتي تتطابق مع رمز الآلة المستخدَم في صورة النظام نفسها
- اختياريًا، واجهات التطبيق الثنائية الثانوية، التي تتطابق مع واجهات التطبيق الثنائية الأخرى التي تتيحها صورة النظام أيضًا
تضمن هذه الآلية أنّ النظام يستخرج أفضل رمز آلة من الحزمة أثناء التثبيت.
للحصول على أفضل أداء، عليك إجراء عملية التحويل البرمجي مباشرةً لواجهة التطبيق الثنائية الأساسية. على سبيل المثال، لن يحدّد جهاز نموذجي مستند إلى ARMv5TE سوى واجهة التطبيق الثنائية الأساسية: armeabi. في المقابل، سيحدّد جهاز نموذجي مستند إلى ARMv7 واجهة التطبيق الثنائية الأساسية على أنّها armeabi-v7a والواجهة الثانوية على أنّها armeabi، لأنّه يمكنه تشغيل الملفات الثنائية الأصلية للتطبيق التي تم إنشاؤها لكل منهما.
تتيح الأجهزة 64 بت أيضًا استخدام أنواعها 32 بت. باستخدام أجهزة arm64-v8a كمثال، يمكن للجهاز أيضًا تشغيل رمز armeabi وarmeabi-v7a. ومع ذلك، يُرجى العِلم أنّ أداء تطبيقك سيكون أفضل بكثير على الأجهزة 64 بت إذا كان يستهدف arm64-v8a بدلاً من الاعتماد على تشغيل الجهاز لإصدار armeabi-v7a من تطبيقك.
يمكن للعديد من الأجهزة المستندة إلى x86 أيضًا تشغيل الملفات الثنائية لـ armeabi-v7a وarmeabi في حزمة تطوير التطبيقات الأصلية (NDK). بالنسبة إلى هذه الأجهزة، ستكون واجهة التطبيق الثنائية الأساسية هي x86، والواجهة الثانوية هي armeabi-v7a.
يمكنك فرض تثبيت ملف 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 عادةً.
يُرجى الاطّلاع على Learn the architecture - Providing protection for complex software (PDF) من Arm للحصول على شرح مفصّل لأساليب الهجوم التي تستهدفها 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. إذا كنت تستخدم إدارة الحقوق الرقمية للتطبيقات وتواجه مشاكل عند تفعيل PAC/BTI، يُرجى التواصل مع مورّد إدارة الحقوق الرقمية للحصول على إصدار صالح.