Android ABI

تستخدم أجهزة Android المختلفة وحدات معالجة مركزية مختلفة، والتي بدورها تدعم مجموعات التعليمات المختلفة. لكل مجموعة من وحدة المعالجة المركزية (CPU) ومجموعة التعليمات واجهة التطبيق الثنائية (ABI) الخاصة بها. يتضمن واجهة التطبيق الثنائية (ABI) المعلومات التالية:

  • مجموعة تعليمات وحدة المعالجة المركزية (CPU) (والإضافات) التي يمكن استخدامها.
  • مدى قابلية تخزين الذاكرة وتحميلها في وقت التشغيل. دائمًا ما يكون Android عصيًا قليلاً.
  • يشير ذلك المصطلح إلى الاصطلاحات المتّبَعة لتمرير البيانات بين التطبيقات والنظام، بما في ذلك قيود المحاذاة، وكيفية استخدام النظام للحزمة والتسجيل عند استدعاء الدوال.
  • تنسيق البرامج الثنائية القابلة للتنفيذ، مثل البرامج والمكتبات المشتركة، وأنواع المحتوى التي تدعمها. يستخدم Android دائمًا ELF. لمزيد من المعلومات، يُرجى الاطّلاع على واجهة التطبيق الثنائية لنظام ELF System V.
  • كيفية تشويه أسماء C++. لمزيد من المعلومات، يُرجى الاطّلاع على القسم عامة/Itanium C++ ABI.

تسرد هذه الصفحة واجهات التطبيق الثنائية (ABI) التي تتوافق معها NDK، كما تقدّم معلومات حول طريقة عمل كل واجهة تطبيق ABI.

يمكن أن تشير واجهة التطبيق الثنائية (ABI) أيضًا إلى واجهة برمجة التطبيقات الأصلية التي يوفّرها النظام الأساسي. للحصول على قائمة بهذه الأنواع من مشاكل واجهة التطبيق الثنائية (ABI) التي تؤثر في أنظمة 32 بت، راجِع أخطاء واجهة التطبيق الثنائية (ABI) بسعة 32 بت

واجهات التطبيق الثنائية (ABI) المتوافقة

الجدول 1. واجهات التطبيق الثنائية (ABI) ومجموعات التعليمات المتوافقة

قيمة ABI مجموعات التعليمات المتوافقة Notes
armeabi-v7a
  • أرميبي
  • الإبهام 2
  • نيون
  • غير متوافقة مع الأجهزة المزودة بالإصدار ARMv5 أو الإصدار 6.
    arm64-v8a
  • AArch64
  • Armv8.0 فقط
    x86
  • x86 (IA-32)
  • مبادل البريد (MMX)
  • جنوبية إلى 2/3
  • معيار SSSE3
  • لا يوجد دعم لـ MOVBE أو SSE4.
    x86_64
  • x86-64
  • مبادل البريد (MMX)
  • جنوبية إلى 2/3
  • معيار SSSE3
  • SSE4.1، 4.2
  • بروتوكول POPCNT
  • x86-64-v1 فقط

    ملاحظة: سابقًا، كان NDK يتوافق مع ARMv5 (armeabi)، فضلاً عن إصدار 32 بت و64 بت MIPS، ولكن تمت إزالة التوافق مع واجهات التطبيق الثنائية (ABI) هذه في الإصدار NDK r17.

    armeabi-v7a

    تعمل واجهة التطبيق الثنائية (ABI) هذه مع وحدات المعالجة المركزية ARM ذات 32 بت. وهي تشمل الإبهام 2 ونيون.

    للحصول على معلومات عن أجزاء واجهة التطبيق الثنائية (ABI) التي لا تخصّ Android، يُرجى الاطّلاع على واجهة التطبيق الثنائية (ABI) الخاصة ببنية ARM.

    تنشئ أنظمة تصميم NDK رمز Thumb-2 تلقائيًا إلا إذا كنت تستخدم LOCAL_ARM_MODE في Android.mk في ndk-build أو ANDROID_ARM_MODE عند ضبط CMake.

    للحصول على مزيد من المعلومات عن سجل نيون، راجع دعم نيون.

    لأسباب قديمة، تستخدم واجهة التطبيق الثنائية (ABI) هذه السمة -mfloat-abi=softfp التي تؤدي إلى تمرير جميع قيم float في سجلات الأعداد الصحيحة وتمرير جميع قيم double في أزواج سجلات الأعداد الصحيحة عند إجراء استدعاءات الدوال. وعلى الرغم من الاسم، هذا لا يؤثر إلا على اصطلاح تسمية النقطة العائمة: سيظل المحول البرمجي يستخدمون تعليمات النقاط العائمة للأجهزة لإجراء العمليات الحسابية.

    تستخدم واجهة التطبيق الثنائية (ABI) هذه long double64 بت (معيار IEEE ثنائي64 مماثلاً لـ double).

    المستوى 64-v8a

    تعمل واجهة التطبيق الثنائية (ABI) هذه مع وحدات المعالجة المركزية ARM 64 بت.

    يُرجى الاطّلاع على ملف التعرُّف على البنية الأساسية في Arm للحصول على تفاصيل كاملة عن أجزاء واجهة التطبيق الثنائية (ABI) التي لا تتعلّق بنظام التشغيل Android. تقدم شركة Arm أيضًا بعض النصائح المتعلقة بالنقل في تطوير Android 64 بت.

    يمكنك استخدام أساسيات نيون في الرموز C وC++ للاستفادة من إضافة SIMD المتقدمة. يوفّر دليل مبرمج نيون لتقنية Armv8-A مزيدًا من المعلومات حول أساسيات نيون وبرمجة نيون بشكل عام.

    على Android، يكون سجل x18 الخاص بالنظام الأساسي محجوزًا لـ ShadowCallStack ويجب ألا يتأثر بالرمز البرمجي. تستخدم الإصدارات الحالية من Clang تلقائيًا خيار -ffixed-x18 على Android، ولذلك لا داعي للقلق بهذا الشأن ما لم يكن لديك مجمّع مكتوب بخط اليد (أو برنامج تجميع قديم جدًا).

    تستخدم واجهة التطبيق الثنائية (ABI) هذه رمز long double 128 بت (ثنائي IEEE 128).

    جهاز x86

    إن واجهة التطبيق الثنائية (ABI) هذه مخصصة لوحدات المعالجة المركزية (CPU) التي تتوافق مع مجموعة التعليمات المعروفة عادةً باسم "x86" أو "i386" أو "IA-32".

    تتضمّن واجهة التطبيق الثنائية (ABI) في Android مجموعة التعليمات الأساسية بالإضافة إلى الإضافات MMX وSSE وSSE2 وSSE3 وSSSE3.

    لا تتضمّن واجهة التطبيق الثنائية (ABI) أيّ إضافات اختيارية أخرى لمجموعات تعليمات IA-32، مثل MOVBE أو أيّ صيغة من SSE4. سيظل بإمكانك استخدام هذه الإضافات ما دمت تستخدم ميزات وقت التشغيل لتفعيلها مع توفير إضافات احتياطية للأجهزة غير المتوافقة معها.

    تفترض سلسلة أدوات NDK محاذاة تكديس 16 بايت قبل استدعاء الدالة. الأدوات والخيارات الافتراضية تفرض هذه القاعدة. إذا كنت تكتب تعليمات التجميع، فيجب التأكد من الحفاظ على محاذاة المكدس، والتأكد من أن برامج التحويل البرمجي الأخرى تتبع هذه القاعدة أيضًا.

    يُرجى الاطّلاع على المستندات التالية لمزيد من التفاصيل:

    تستخدم واجهة التطبيق الثنائية (ABI) هذه واجهة long double بمعيار 64 بت (برنامج ثنائي IEEE 64 مماثلاً لنظام double، وليس واجهة برمجة التطبيقات long double التي تستخدم معالجات Intel فقط بنظام 80 بت الأكثر شيوعًا).

    x86_64

    تجدر الإشارة إلى أن واجهة التطبيق الثنائية (ABI) هذه مخصصة لوحدات المعالجة المركزية التي تتوافق مع مجموعة التعليمات التي يُشار إليها عادةً باسم "x86-64".

    تتضمن واجهة التطبيق الثنائية (ABI) في Android مجموعة التعليمات الأساسية بالإضافة إلى MMX وSSE وSSE2 وSSE3 وSSSE3 وSSE4.1 وSSE4.2 وتعليمات POPCNT.

    لا تتضمّن واجهة التطبيق الثنائية (ABI) أي إضافات اختيارية أخرى لمجموعات تعليمات x86-64 مثل MOVBE أو SHA أو أي صيغة أخرى من AVX. سيظل بإمكانك استخدام هذه الإضافات ما دمت تستخدم اختبارات ميزة وقت التشغيل لتفعيلها، مع توفير إضافات احتياطية للأجهزة غير المتوافقة معها.

    يُرجى الاطّلاع على المستندات التالية لمزيد من التفاصيل:

    تستخدم واجهة التطبيق الثنائية (ABI) هذه رمز long double 128 بت (ثنائي IEEE 128).

    إنشاء رمز لواجهة برمجة تطبيقات محدَّدة

    مبرّد

    تعمل أداة 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.

    الإنشاء

    باستخدام CMake، يمكنك إنشاء واجهة ABI واحدة في كل مرة ويجب أن تحدد واجهة 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 بالدهون. إنّ حزمة APK الدهينة أكبر بكثير من تلك التي تحتوي على برامج ثنائية فقط لواجهة التطبيق الثنائية (ABI) واحدة، فالمفاضلة تزداد توافقًا على نطاق أوسع، ولكن على حساب حزمة APK أكبر. ننصحك بشدة بالاستفادة من حِزم التطبيقات أو تقسيمات APK لتقليل حجم حِزم APK مع الحفاظ على أقصى قدر من التوافق مع الأجهزة.

    في وقت التثبيت، يقوم مدير الحزم بفك ضغط رمز الجهاز الأكثر ملاءمة فقط للجهاز المستهدف. لمعرفة التفاصيل، يُرجى الاطّلاع على الاستخراج التلقائي للرموز البرمجية الأصلية أثناء التثبيت.

    إدارة واجهة التطبيق الثنائية (ABI) على نظام Android الأساسي

    يقدّم هذا القسم تفاصيل حول كيفية إدارة نظام Android الأساسي للرموز البرمجية الأصلية في حِزم APK.

    الرموز البرمجية الأصلية في حِزم التطبيقات

    يتوقع كل من "متجر Play" و"مدير الحزم" العثور على مكتبات تم إنشاؤها عن طريق الخطأ بدون تصنيف (NDK) على مسارات الملفات داخل حزمة APK التي تتطابق مع النمط التالي:

    /lib/<abi>/lib<name>.so
    

    هنا، الاسم <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
    

    ملاحظة: إنّ أجهزة Android المستندة إلى ARMv7 والتي تعمل بالإصدار 4.0.3 أو الإصدارات الأقدم تثبِّت المكتبات الأصلية من دليل armeabi بدلاً من الدليل 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 بت إذا كان يستهدف bar64-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 التي لا تتخذ أي إجراء على المعالجات القديمة التي لا تتوافق مع التعليمات الجديدة. ستحصل الأجهزة التي تتضمّن معالجات ARMv9 فقط على حماية PAC/BTI، ولكن يمكنك تشغيل الرمز نفسه على الأجهزة التي تتضمّن معالجات ARMv8 أيضًا، وبالتالي ليس هناك حاجة إلى إصدارات متعددة من مكتبتك. حتى على أجهزة ARMv9، لا ينطبق PAC/BTI إلا على رمز 64 بت.

    سيؤدي تفعيل PAC/BTI إلى زيادة طفيفة في حجم الرمز، تبلغ عادةً %1.

    يمكنك الاطّلاع على مقالة التعرّف على البنية الأساسية - توفير الحماية للبرامج المعقّدة (PDF) للحصول على شرح تفصيلي لهدف موجّهات PAC/BTI المستهدفة وطريقة عمل الحماية.

    إنشاء التغييرات

    إنشاء ndk

    يجب ضبط LOCAL_BRANCH_PROTECTION := standard في كل وحدة من وحدات Android.mk.

    الإنشاء

    استخدِم target_compile_options($TARGET PRIVATE -mbranch-protection=standard) لكل هدف في CMakeLists.txt.

    أنظمة التصميم الأخرى

    اجمع الرمز باستخدام -mbranch-protection=standard. لا تعمل هذه العلامة إلا عند إنشاء واجهة برمجة التطبيقات Arm64-v8a ABI. لست بحاجة إلى استخدام هذه العلامة عند الربط.

    تحديد المشاكل وحلّها

    لسنا على علم بأي مشاكل في دعم برنامج التحويل البرمجي لـ PAC/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، يمكنك التواصل مع مورّد نظام إدارة الحقوق الرقمية للحصول على إصدار ثابت.