دستگاههای مختلف اندروید از CPU های متفاوتی استفاده میکنند که به نوبه خود از مجموعه دستورالعملهای متفاوتی پشتیبانی میکنند. هر ترکیبی از CPU و مجموعه دستورالعمل، رابط دودویی برنامه (ABI) مخصوص به خود را دارد. یک ABI شامل اطلاعات زیر است:
- مجموعه دستورالعملهای CPU (و افزونههای آن) که میتوانند مورد استفاده قرار گیرند.
- اندیان بودن حافظه، ذخیرهسازی و بارگذاری در زمان اجرا. اندروید همیشه به صورت Little-endian است.
- قراردادهایی برای انتقال دادهها بین برنامهها و سیستم، شامل محدودیتهای همترازی، و نحوه استفاده سیستم از پشته و ثباتها هنگام فراخوانی توابع.
- قالب فایلهای اجرایی باینری، مانند برنامهها و کتابخانههای مشترک، و انواع محتوایی که پشتیبانی میکنند. اندروید همیشه از ELF استفاده میکند. برای اطلاعات بیشتر، به رابط دودویی برنامه سیستم V ELF مراجعه کنید.
- چگونه نامهای C++ دستکاری میشوند. برای اطلاعات بیشتر، به Generic/Itanium C++ ABI مراجعه کنید.
این صفحه ABIهایی را که NDK پشتیبانی میکند، فهرست میکند و اطلاعاتی در مورد نحوه عملکرد هر ABI ارائه میدهد.
ABI همچنین میتواند به API بومی پشتیبانیشده توسط پلتفرم اشاره داشته باشد. برای فهرستی از انواع مشکلات ABI که سیستمهای ۳۲ بیتی را تحت تأثیر قرار میدهند، به بخش اشکالات ABI 32 بیتی مراجعه کنید.
ABI های پشتیبانی شده
جدول 1. ABIها و مجموعه دستورالعملهای پشتیبانیشده
| ای بی آی | مجموعه دستورالعملهای پشتیبانیشده | یادداشتها |
|---|---|---|
armeabi-v7a | با دستگاههای ARMv5/v6 سازگار نیست. | |
arm64-v8a | فقط Armv8.0. | |
x86 | عدم پشتیبانی از MOVBE یا SSE4. | |
x86_64 | نسخه کامل x86-64-v2 . |
توجه: از نظر تاریخی، NDK از ARMv5 (armeabi) و MIPS های 32 بیتی و 64 بیتی پشتیبانی میکرد، اما پشتیبانی از این ABI ها در NDK r17 حذف شد.
armeabi-v7a
این ABI برای پردازندههای ARM 32 بیتی است و شامل Thumb-2 و Neon میشود.
برای اطلاعات بیشتر در مورد بخشهایی از ABI که مختص اندروید نیستند، به رابط دودویی برنامه (ABI) برای معماری ARM مراجعه کنید.
سیستمهای ساخت NDK به طور پیشفرض کد Thumb-2 تولید میکنند، مگر اینکه از LOCAL_ARM_MODE در Android.mk خود برای ndk-build یا ANDROID_ARM_MODE هنگام پیکربندی CMake استفاده کنید.
برای اطلاعات بیشتر در مورد تاریخچه نئون، به پشتیبانی نئون مراجعه کنید.
به دلایل تاریخی، این ABI از -mfloat-abi=softfp استفاده میکند که باعث میشود هنگام فراخوانی توابع، تمام مقادیر float در رجیسترهای عدد صحیح و تمام مقادیر double در جفت رجیسترهای عدد صحیح ارسال شوند. علیرغم نام، این فقط بر قرارداد فراخوانی ممیز شناور تأثیر میگذارد: کامپایلر همچنان از دستورالعملهای ممیز شناور سختافزاری برای محاسبات استفاده خواهد کرد.
این ABI از یک نوع long double ۶۴ بیت استفاده میکند ( IEEE binary64 مشابه double است).
arm64-v8a
این ABI برای پردازندههای ARM 64 بیتی است.
برای جزئیات کامل بخشهایی از ABI که مختص اندروید نیستند، به بخش «یادگیری معماری» در Arm مراجعه کنید. Arm همچنین توصیههایی در مورد پورت کردن در توسعه اندروید ۶۴ بیتی ارائه میدهد.
You can use Neon intrinsics in C and C++ code to take advantage of the Advanced SIMD extension. The Neon Programmer's Guide for Armv8-A provides more information about Neon intrinsics and Neon programming in general.
در اندروید، رجیستر x18 مختص پلتفرم برای ShadowCallStack رزرو شده است و نباید توسط کد شما دستکاری شود. نسخههای فعلی Clang به طور پیشفرض از گزینه -ffixed-x18 در اندروید استفاده میکنند، بنابراین، مگر اینکه اسمبلر دستنویس (یا کامپایلر بسیار قدیمی) داشته باشید، نیازی به نگرانی در مورد این موضوع نیست.
این ABI از یک long double ( IEEE binary128 ) با طول ۱۲۸ بیت استفاده میکند.
ایکس۸۶
این ABI برای پردازندههایی است که از مجموعه دستورالعملهای رایج "x86"، "i386" یا "IA-32" پشتیبانی میکنند.
ABI اندروید شامل مجموعه دستورالعملهای پایه به علاوهی افزونههای MMX ، SSE ، SSE2 ، SSE3 و SSSE3 است.
ABI شامل هیچ افزونهی اختیاری دیگری برای مجموعه دستورالعملهای IA-32 مانند MOVBE یا هیچ گونهی دیگری از SSE4 نیست. شما همچنان میتوانید از این افزونهها استفاده کنید، البته تا زمانی که از قابلیت جستجوی ویژگی در زمان اجرا برای فعال کردن آنها استفاده کنید و برای دستگاههایی که از آنها پشتیبانی نمیکنند، جایگزینهایی را فراهم کنید.
زنجیره ابزار NDK قبل از فراخوانی تابع، همترازی پشته ۱۶ بایتی را در نظر میگیرد. ابزارها و گزینههای پیشفرض این قانون را اجرا میکنند. اگر کد اسمبلی مینویسید، باید مطمئن شوید که همترازی پشته را حفظ میکنید و مطمئن شوید که سایر کامپایلرها نیز از این قانون پیروی میکنند.
برای جزئیات بیشتر به اسناد زیر مراجعه کنید:
- قراردادهای فراخوانی برای کامپایلرها و سیستم عاملهای مختلف C++
- اینتل IA-32 راهنمای توسعهدهندگان نرمافزار معماری اینتل، جلد 2: مرجع مجموعه دستورالعملها
- دفترچه راهنمای توسعهدهندگان نرمافزار معماری اینتل IA-32، جلد 3: راهنمای برنامهنویسی سیستم
- رابط دودویی برنامه کاربردی System V: معماری پردازنده Intel386، ضمیمه
این ABI از یک long double ۶۴ بیت استفاده میکند ( IEEE binary64 مشابه double است و نه long double رایجتر ۸۰ بیتی که فقط برای اینتل استفاده میشود).
x86_64
این ABI برای پردازندههایی است که از مجموعه دستورالعملهایی که معمولاً به عنوان "x86-64" شناخته میشوند، پشتیبانی میکنند.
ABI اندروید شامل مجموعه دستورالعملهای پایه به علاوه MMX ، SSE ، SSE2 ، SSE3 ، SSSE3 ، SSE4.1 ، SSE4.2 و دستورالعمل POPCNT است.
ABI شامل هیچ افزونهی اختیاری دیگری برای مجموعه دستورالعملهای x86-64 مانند MOVBE، SHA یا هیچ نوع AVX نیست. شما همچنان میتوانید از این افزونهها استفاده کنید، البته تا زمانی که از قابلیت کاوش در زمان اجرا برای فعال کردن آنها استفاده کنید و برای دستگاههایی که از آنها پشتیبانی نمیکنند، جایگزینهایی ارائه دهید.
برای جزئیات بیشتر به اسناد زیر مراجعه کنید:
- قراردادهای فراخوانی برای کامپایلرها و سیستم عاملهای مختلف C++
- راهنمای توسعهدهندگان نرمافزار معماریهای Intel64 و IA-32، جلد 2: مرجع مجموعه دستورالعملها
- دفترچه راهنمای توسعهدهندگان نرمافزار معماری اینتل Intel64 و IA-32، جلد 3: برنامهنویسی سیستم
این ABI از یک long double ( IEEE binary128 ) با طول ۱۲۸ بیت استفاده میکند.
تولید کد برای یک ABI خاص
گرادل
Gradle (whether used via Android Studio or from the command line) builds for all non-deprecated ABIs by default. To restrict the set of ABIs that your application supports, use abiFilters . For example, to build for only 64-bit ABIs, set the following configuration in your build.gradle :
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ساخت ndk
ndk-build به طور پیشفرض برای همه ABI های غیر منسوخ شده ساخته میشود. میتوانید با تنظیم APP_ABI در فایل Application.mk خود، 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، شما برای یک 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 ...
برای سایر پرچمهایی که باید برای ساخت با NDK به CMake ارسال شوند، به راهنمای CMake مراجعه کنید.
رفتار پیشفرض سیستم ساخت، گنجاندن فایلهای باینری برای هر ABI در یک APK واحد است که به عنوان APK حجیم نیز شناخته میشود. یک APK حجیم به طور قابل توجهی بزرگتر از APK است که فقط شامل فایلهای باینری برای یک ABI واحد است. این مزیت، سازگاری گستردهتری را به همراه دارد، اما به قیمت یک APK بزرگتر. اکیداً توصیه میشود که از App Bundles یا APK Splits برای کاهش اندازه APK های خود استفاده کنید و در عین حال حداکثر سازگاری دستگاه را حفظ کنید.
در زمان نصب، مدیر بسته فقط مناسبترین کد ماشین را برای دستگاه هدف از حالت فشرده خارج میکند. برای جزئیات بیشتر، به بخش استخراج خودکار کد بومی در زمان نصب مراجعه کنید.
مدیریت ABI در پلتفرم اندروید
این بخش جزئیاتی در مورد نحوه مدیریت کد بومی در فایلهای APK توسط پلتفرم اندروید ارائه میدهد.
کد بومی در بستههای برنامه
هم پلی استور و هم پکیج منیجر انتظار دارند کتابخانههای تولید شده توسط NDK را در مسیرهای فایل درون APK با الگوی زیر پیدا کنند:
/lib/<abi>/lib<name>.so
در اینجا، <abi> یکی از نامهای ABI است که در بخش Supported ABIs فهرست شده است و <name> نام کتابخانهای است که شما برای متغیر LOCAL_MODULE در فایل Android.mk تعریف کردهاید. از آنجایی که فایلهای APK فقط فایلهای زیپ هستند، باز کردن آنها و تأیید اینکه کتابخانههای بومی مشترک در جای خود قرار دارند، کار سادهای است.
اگر سیستم کتابخانههای اشتراکی بومی را در جایی که انتظار دارد پیدا نکند، نمیتواند از آنها استفاده کند. در چنین حالتی، خود برنامه باید کتابخانهها را کپی کند و سپس 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
توجه: دستگاههای اندروید مبتنی بر ARMv7 که نسخه ۴.۰.۳ یا قدیمیتر را اجرا میکنند، در صورت وجود هر دو دایرکتوری، کتابخانههای بومی را به جای دایرکتوری armeabi-v7a از دایرکتوری armeabi نصب میکنند. دلیل این امر این است که /lib/armeabi/ در APK بعد از /lib/armeabi-v7a/ میآید. این مشکل از نسخه ۴.۰.۴ برطرف شده است.
پشتیبانی از ABI پلتفرم اندروید
سیستم اندروید در زمان اجرا میداند که از کدام ABI(ها) پشتیبانی میکند، زیرا ویژگیهای سیستمیِ مختصِ ساخت، موارد زیر را نشان میدهند:
- ABI اصلی برای دستگاه، مربوط به کد ماشین مورد استفاده در خود تصویر سیستم.
- به صورت اختیاری، ABI های ثانویه، مربوط به سایر ABI هایی که تصویر سیستم از آنها پشتیبانی می کند.
این مکانیزم تضمین میکند که سیستم در زمان نصب، بهترین کد ماشین را از بسته استخراج کند.
برای بهترین عملکرد، باید مستقیماً برای ABI اصلی کامپایل کنید. برای مثال، یک دستگاه معمولی مبتنی بر ARMv5TE فقط ABI اصلی را تعریف میکند: armeabi . در مقابل، یک دستگاه معمولی مبتنی بر ARMv7 ABI اصلی را armeabi-v7a و ABI ثانویه را armeabi تعریف میکند، زیرا میتواند فایلهای باینری بومی برنامه را که برای هر یک از آنها تولید شده است، اجرا کند.
دستگاههای ۶۴ بیتی از انواع ۳۲ بیتی خود نیز پشتیبانی میکنند. به عنوان مثال، با استفاده از دستگاههای arm64-v8a، این دستگاه میتواند کدهای armeabi و armeabi-v7a را نیز اجرا کند. با این حال، توجه داشته باشید که اگر برنامه شما 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 را بازیابی میکنند:
کاتلین
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}")
جاوا
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 با الزام اینکه هر هدف شاخه یک دستورالعمل ویژه است که کاری جز گفتن به پردازنده مبنی بر اینکه رفتن به آنجا اشکالی ندارد، انجام نمیدهد، از پرش به مکانهای دلخواه در کد شما جلوگیری میکند.
اندروید از دستورالعملهای PAC/BTI استفاده میکند که روی پردازندههای قدیمیتر که از دستورالعملهای جدید پشتیبانی نمیکنند، هیچ کاری انجام نمیدهند. فقط دستگاههای ARMv9 دارای محافظت PAC/BTI هستند، اما میتوانید همان کد را روی دستگاههای ARMv8 نیز اجرا کنید: نیازی به چندین نوع کتابخانه نیست. حتی در دستگاههای ARMv9، PAC/BTI فقط روی کد ۶۴ بیتی اعمال میشود.
فعال کردن PAC/BTI باعث افزایش جزئی در اندازه کد، معمولاً ۱٪، میشود.
برای توضیح دقیق در مورد بردارهای حمله هدف PAC/BTI و نحوه عملکرد محافظت، به مقاله «معماری را بیاموزید - ارائه محافظت برای نرمافزارهای پیچیده » از شرکت آرم ( PDF ) مراجعه کنید.
تغییرات را بسازید
ساخت ndk
LOCAL_BRANCH_PROTECTION := standard تنظیم کنید.
سیمیک
برای هر هدف در CMakeLists.txt خود target_compile_options($TARGET PRIVATE -mbranch-protection=standard) استفاده کنید.
سایر سیستمهای ساخت
کد خود را با استفاده از -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 (قبل از ۱.۱.۱i) دارای یک اشکال در اسمبلر دستنویس هستند که باعث خرابی PAC میشود. به OpenSSL فعلی ارتقا دهید.
نسخههای قدیمی برخی از سیستمهای DRM برنامه، کدی تولید میکنند که الزامات PAC/BTI را نقض میکند. اگر از DRM برنامه استفاده میکنید و هنگام فعال کردن PAC/BTI با مشکلاتی مواجه شدید، برای دریافت نسخه اصلاحشده با فروشنده DRM خود تماس بگیرید.