تستخدم أجهزة Android المختلفة وحدات معالجة مركزية مختلفة، والتي بدورها تتوافق مع مجموعات التعليمات المختلفة. يكون لكل مجموعة من وحدة المعالجة المركزية (CPU) ومجموعة التعليمات واجهة التطبيق الثنائية (ABI) الخاصة بها. يتضمن ABI المعلومات التالية:
- مجموعة تعليمات وحدة المعالجة المركزية (CPU) (والإضافات) التي يمكن استخدامها.
- مدى صلاحية تخزين الذاكرة وتحميلها أثناء وقت التشغيل. دائمًا ما يكون Android محدودًا.
- يشير ذلك المصطلح إلى الاصطلاحات الخاصة بتمرير البيانات بين التطبيقات والنظام، بما في ذلك قيود المحاذاة وطريقة استخدام النظام للحزمة والتسجيل عند استدعاء الدوال.
- يشير إلى تنسيق البرامج الثنائية القابلة للتنفيذ، مثل البرامج والمكتبات المشتركة، وأنواع المحتوى التي تدعمها. يستخدم Android دائمًا ELF. لمزيد من المعلومات، يُرجى الاطّلاع على الواجهة الثنائية لتطبيق ELF System V.
- كيفية تشويه أسماء C++. لمزيد من المعلومات، يُرجى الاطّلاع على General/Itanium C++ ABI.
وتسرد هذه الصفحة واجهات التطبيق الثنائية (ABI) التي تدعمها NDK، كما توفر معلومات حول كيفية عمل كل واجهة تطبيق ثنائية (ABI).
ويمكن أن تشير واجهة التطبيق الثنائية (ABI) أيضًا إلى واجهة برمجة التطبيقات الأصلية التي توفّرها المنصة. للحصول على قائمة بهذه الأنواع من مشاكل واجهة التطبيق الثنائية (ABI) التي تؤثر في أنظمة 32 بت، راجِع أخطاء ABI ذات 32 بت.
واجهات ABI المتوافقة
الجدول 1. واجهات ABI ومجموعات التعليمات المتوافقة
قيمة ABI | مجموعات التعليمات المتوافقة | Notes |
---|---|---|
armeabi-v7a |
|
غير متوافقة مع الأجهزة التي تتضمّن معالجات ARMv5 أو V6. |
arm64-v8a |
Armv8.0 فقط | |
x86 |
ولا يدعم MOVBE أو SSE4. | |
x86_64 |
|
x86-64-v1 فقط. |
ملاحظة: سابقًا، كان NDK متوافق مع ARMv5 (armeabi)، ووحدات MIPS 32 بت و64 بت، ولكن تمت إزالة دعم ABI هذه في NDK r17.
armeabi-v7a
وتعمل واجهة التطبيق الثنائية (ABI) هذه مع وحدات المعالجة المركزية ARM ذات 32 بت. وهي تشتمل على تعليمات النقطة العائمة لأجهزة Thumb-2 وNeon (VFP)، وتحديدًا VFPv3-D16 مع 16 سجل نقاط عائمة مخصص 64 بت.
للحصول على معلومات عن أجزاء واجهة التطبيق الثنائية (ABI) غير الخاصة بنظام التشغيل Android، يُرجى الاطّلاع على واجهة التطبيق الثنائية (ABI) لبنية ARM.
تنشئ أنظمة إصدار NDK رمز Thumb-2 تلقائيًا إلا إذا كنت تستخدم LOCAL_ARM_MODE
في Android.mk
في ndk-build أو ANDROID_ARM_MODE
عند إعداد CMake.
الإضافات الأخرى، بما في ذلك شريحة SIMD المتقدمة (نيون) وVFPv3-D32 اختيارية. لمزيد من المعلومات، يُرجى الاطّلاع على دعم Neon.
تستخدم واجهة التطبيق الثنائية (ABI) هذه السمة -mfloat-abi=softfp
لفرض القاعدة التي تنص على أن المحوِّل البرمجي يجب أن يجتاز جميع قيم float
في السجلات الصحيحة وأن جميع قيم double
في أزواج سجلات العدد الصحيح عند إجراء استدعاءات الدوال. يؤثر ذلك فقط على
اصطلاح الاتصال. سيظل المحول البرمجي يستخدم تعليمات النقطة العائمة للأجهزة.
وتستخدم واجهة التطبيق الثنائية (ABI) هذه long double
64 بت (IEEE ثنائي64، تمامًا مثل double
).
Arm64-v8a
وتعمل واجهة التطبيق الثنائية (ABI) هذه مع وحدات المعالجة المركزية ARM ذات 64 بت.
يمكنك الاطّلاع على قسم Learn the الهندسة المعمارية في تطبيق Arm للحصول على تفاصيل كاملة عن أجزاء واجهة التطبيق الثنائية (ABI) غير الخاصة بنظام التشغيل Android. تقدم Arm أيضًا بعض النصائح بشأن النقل في تطوير Android إصدار 64 بت.
يمكنك استخدام أساسيات نيون في الرموز C وC++ للاستفادة من الإضافة المتقدمة لشريحة SIMD. يقدّم دليل مُبرمَج النيون لـ Armv8-A مزيدًا من المعلومات حول أساسيات Neon وبرمجة Neon بشكل عام.
في Android، يتم حجز سجل x18 الخاص بالنظام الأساسي لـ
ShadowCallStack
ويجب عدم لمسه بالرمز. يتم استخدام خيار -ffixed-x18
على Android تلقائيًا في الإصدارات الحالية من Clang، ولذلك لا داعي للقلق بهذا الشأن ما لم يكن لديك مجمّع مكتوب بخط اليد (أو برنامج تجميع قديم جدًا).
تستخدم واجهة التطبيق الثنائية (ABI) هذه long double
128 بت (IEEE ثنائي 128).
x86
إنّ واجهة التطبيق الثنائية (ABI) هذه مخصّصة لوحدات المعالجة المركزية التي تتوافق مع مجموعة التعليمات المعروفة باسم "x86" أو "i386" أو "IA-32".
تشتمل واجهة التطبيق الثنائية (ABI) على مجموعة التعليمات الأساسية بالإضافة إلى الإضافات MMX وSSE وSSE2 وSSE3 وSSSE3.
لا يشمل واجهة ABI أي إضافات اختيارية أخرى لمجموعة تعليمات IA-32، مثل MOVBE أو أي متغير من SSE4. لا يزال بإمكانك استخدام هذه الإضافات طالما أنّك تستخدم ميزة التحقّق من ميزات وقت التشغيل لتفعيلها، مع توفير إضافات احتياطية للأجهزة التي لا تتوافق معها.
تفترض سلسلة أدوات NDK محاذاة تكديس 16 بايت قبل استدعاء الدالة. تفرض الأدوات والخيارات الافتراضية هذه القاعدة. إذا كنت تكتب كود المجموعة، فيجب التأكد من الحفاظ على محاذاة المكدس، والتأكد من أن المحولات البرمجية الأخرى تتبع هذه القاعدة أيضًا.
يُرجى الاطّلاع على المستندات التالية للحصول على مزيد من التفاصيل:
- اصطلاحات الاستدعاء للمحولات البرمجية المختلفة وأنظمة التشغيل لـ C++
- دليل المطوّر لبرامج Intel IA-32 Intel Architecture Software، الجزء 2: مرجع مجموعة التعليمات
- دليل مطوّري برامج Intel IA-32 Intel Architecture Software، الجزء 3: دليل برمجة النظام
- النظام الثنائي لتطبيق System V الواجهة: ملحق بنية معالج Intel386
تستخدم واجهة التطبيق الثنائية (ABI) هذه long double
الإصدار 64 بت (وهو برنامج ثنائي IEEE 64 مثل double
، وليس واجهة برمجة التطبيقات long double
الأكثر شيوعًا التي تعمل بـ Intel فقط بنظام 80 بت).
x86_64
إنّ واجهة التطبيق الثنائية (ABI) هذه مخصَّصة لوحدات المعالجة المركزية (CPU) التي تتوافق مع مجموعة التعليمات التي يُشار إليها عادةً باسم "x86-64".
تشمل واجهة التطبيق الثنائية (ABI) في Android مجموعة التعليمات الأساسية بالإضافة إلى MMX وSSE وSSE2 وSSE3 وSSSE3 وSSE4.1 وSSE4.2 وتعليمات POPCNT.
لا يتضمّن واجهة التطبيق الثنائية (ABI) أي إضافات اختيارية أخرى لمجموعة تعليمات x86-64، مثل MOVBE أو SHA أو أي صيغة أخرى من AVX. لا يزال بإمكانك استخدام هذه الإضافات طالما أنك تستخدم التحقّق من ميزات وقت التشغيل لتفعيلها، مع توفير إضافات احتياطية للأجهزة التي لا تدعمها.
يُرجى الاطّلاع على المستندات التالية للحصول على مزيد من التفاصيل:
- اصطلاحات الاستدعاء للمحولات البرمجية وأنظمة التشغيل المختلفة لـ C++
- دليل مطوّر البرامج الخاص بهندسة Intel64 وIA-32، الجزء 2: التعليمات المرجعية
- الحجم اليدوي لمطوِّر برامج Intel Architecture من Intel64 وIA-32: برمجة النظام
تستخدم واجهة التطبيق الثنائية (ABI) هذه long double
128 بت (IEEE ثنائي 128).
إنشاء رمز لواجهة تطبيق ABI محدّدة
جرس
تُنشئ أداة Gradle تلقائيًا (سواء تم استخدامها من خلال "استوديو Android" أو من سطر الأوامر) لجميع واجهات ABI التي لم يتم إيقافها نهائيًا. لتقييد مجموعة واجهات ABI التي
يتوافق معها تطبيقك، استخدِم abiFilters
. على سبيل المثال، لإنشاء واجهات ABI 64 بت فقط، عليك ضبط الإعدادات التالية في build.gradle
:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
إصدار ndk
إصدارات ndk-build لجميع واجهات ABI غير المتوقفة تلقائيًا. يمكنك استهداف واجهات ABI معيّنة
من خلال ضبط 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.
CMaker
باستخدام CMake، يمكنك إنشاء واجهة ABI واحدة في كل مرة ويجب عليك تحديد واجهة التطبيق الثنائية بوضوح. يمكنك إجراء ذلك باستخدام المتغيّر 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 fat. تكون حزمة APK الدهنية أكبر بكثير من تلك التي تحتوي فقط على الملفات الثنائية لواجهة التطبيق الثنائية (ABI) واحدة، حيث تزداد التوافقية على نطاق واسع، ولكن على حساب حزمة APK أكبر. وننصحك بشدة بالاستفادة من حِزم التطبيقات أو تقسيمات APK لتقليل حجم حِزم APK والحفاظ في الوقت نفسه على أعلى مستوى من التوافق مع الأجهزة.
في وقت التثبيت، لا يفك مدير الحزم إلا رمز الجهاز الأكثر ملاءمة للجهاز المستهدف. لمعرفة التفاصيل، يُرجى الاطّلاع على الاستخراج التلقائي للرمز الأصلي أثناء التثبيت.
إدارة واجهة التطبيق الثنائية (ABI) على نظام Android الأساسي
يقدّم هذا القسم تفاصيل حول كيفية إدارة نظام Android الأساسي للرموز البرمجية الأصلية في حِزم APK.
الرمز الأصلي في حِزم التطبيقات
يتوقع كل من "متجر Play" و"مدير الحزمة" العثور على مكتبات تم إنشاؤها عن طريق الخطأ NDK في مسارات الملفات داخل حزمة APK التي تتطابق مع النمط التالي:
/lib/<abi>/lib<name>.so
هنا، <abi>
هو أحد أسماء ABI المدرَجة ضمن واجهات ABI المتوافقة،
و<name>
هو اسم المكتبة كما حدّدته للمتغيّر LOCAL_MODULE
في ملف Android.mk
. وبما أنّ ملفات APK هي مجرد ملفات ZIP، ليس من السهل فتحها والتأكّد من أنّ المكتبات الأصلية المشتركة في أماكنها الأصلية.
وإذا لم يعثر النظام على المكتبات المشتركة الأصلية في الأماكن التي يتوقعها، لن يتمكّن من استخدامها. في هذه الحالة، يجب على التطبيق نفسه نسخ المكتبات مجددًا، ثم تنفيذ الإجراء dlopen()
.
في حزمة APK دهنية، تتوفّر كل مكتبة ضمن دليل يتطابق اسمه مع واجهة التطبيق الثنائية (ABI) المقابلة. على سبيل المثال، قد تحتوي حزِمة APK الكبيرة على ما يلي:
/lib/armeabi/libfoo.so /lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
ملاحظة: في حال توفُّر كلا الدليلين، يمكنك تثبيت المكتبات الأصلية من الدليل armeabi
على أجهزة Android المستندة إلى ARMv7 والتي تعمل بالإصدار 4.0.3 أو الإصدارات الأقدم بدلاً من الدليل armeabi-v7a
. وذلك لأنّ العلامة /lib/armeabi/
تأتي بعد
/lib/armeabi-v7a/
في حزمة APK. تم إصلاح هذه المشكلة من الإصدار 4.0.4.
توافق واجهة التطبيق الثنائية (ABI) في نظام Android الأساسي
يحدّد نظام Android في وقت التشغيل واجهات ABI التي يتوافق معها، لأنّ خصائص النظام الخاصة بالإصدار تشير إلى ما يلي:
- واجهة التطبيق الثنائية (ABI) الأساسية للجهاز، والتي تتوافق مع رمز الجهاز المستخدَم في صورة النظام نفسها.
- تكون واجهات ABI الثانوية اختياريًا، تتوافق مع واجهة التطبيق الثنائية (ABI) الأخرى التي تتوافق معها أيضًا صورة النظام.
وتضمن هذه الآلية أن يستخرج النظام أفضل رمز جهاز من الحزمة في وقت التثبيت.
للحصول على أفضل أداء، عليك تجميع البيانات لواجهة التطبيق الثنائية (ABI) الأساسية مباشرةً. على سبيل المثال، يحدد الجهاز النموذجي المستند إلى ARMv5TE واجهة التطبيق الثنائية (ABI) الأساسية فقط: armeabi
. على النقيض من ذلك، يحدد الجهاز النموذجي المستند إلى ARMv7 واجهة التطبيق الثنائية (ABI) الأساسية على أنها armeabi-v7a
والجهاز الثانوي باسم armeabi
، لأنّه يمكنه تشغيل برامج ثنائية أصلية تم إنشاؤها لكلٍ من هذه الأجهزة.
تتوافق الأجهزة التي تعمل بنظام 64 بت أيضًا مع إصدارات 32 بت الخاصة بها. على سبيل المثال، يمكن للجهاز تنفيذ رمزَي armeabi وarmeabi-v7a باستخدام أجهزة Arm64-v8a. ومع ذلك، يُرجى ملاحظة أنّ تطبيقك سيحقق أداءً أفضل على الأجهزة التي تعمل بالإصدار 64 بت إذا كان يستهدف Arm64-v8a بدلاً من الاعتماد على الجهاز الذي يعمل بالإصدار armeabi-v7a من تطبيقك.
يمكن للعديد من الأجهزة التي تعمل بـ x86 أيضًا تشغيل armeabi-v7a
وarmeabi
ثنائيات NDK. بالنسبة إلى هذه الأجهزة، ستكون واجهة التطبيق الثنائية (ABI) الأساسية هي x86
والثاني armeabi-v7a
.
يمكنك فرض تثبيت حزمة APK مع واجهة ABI محدَّدة. وهذا مفيد للاختبار. استخدِم الأمر التالي:
adb install --abi abi-identifier path_to_apk
الاستخراج التلقائي للرمز البرمجي الأصلي في وقت التثبيت
عند تثبيت تطبيق ما، تفحص خدمة مدير الحزم حزمة APK وتبحث عن أي مكتبات مشتركة بالنموذج:
lib/<primary-abi>/lib<name>.so
في حال عدم العثور على أي واجهة، وكنت قد حددت واجهة ABI ثانوية، ستبحث الخدمة عن المكتبات المشتركة للنموذج:
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 على موقع Arm. (PDF) للحصول على شرح تفصيلي لهدف متجهات الهجوم PAC/BTI وآلية توفير الحماية.
إجراء التغييرات
إصدار ndk
يجب ضبط LOCAL_BRANCH_PROTECTION := standard
في كل وحدة من نظام التشغيل Android.mk.
CMaker
استخدِم target_compile_options($TARGET PRIVATE -mbranch-protection=standard)
لكل هدف في ملف CMakeLists.txt الخاص بك.
أنظمة إصدار أخرى
اجمع الرمز باستخدام -mbranch-protection=standard
. لا تعمل هذه العلامة إلا عند
التجميع لـ Arm64-v8a ABI. لن تحتاج إلى استخدام هذه العلامة
عند الربط.
تحديد المشاكل وحلّها
لسنا على دراية بأي مشاكل في دعم برنامج التحويل البرمجي لـ 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 الحالي.
تنشئ الإصدارات القديمة من بعض أنظمة إدارة الحقوق الرقمية للتطبيقات رمزًا برمجيًا ينتهك متطلبات PAC/BTI. إذا كنت تستخدم نظام إدارة الحقوق الرقمية للتطبيق وظهرت لك مشاكل عند تفعيل PAC/BTI، يُرجى التواصل مع مورّد إدارة الحقوق الرقمية للحصول على إصدار ثابت.