AddressSanitizer (ASan) ابزاری سریع مبتنی بر کامپایلر برای تشخیص اشکالات حافظه در کد بومی است.
ASan موارد زیر را تشخیص میدهد:
- سرریز/کمریز بافر پشته و هیپ
- استفاده از هیپ پس از آزادسازی
- استفاده از پشته در خارج از محدوده
- دو برابر رایگان/وحشی رایگان
ASan هم روی ARM 32 بیتی و هم 64 بیتی، به علاوه x86 و x86-64 اجرا میشود. سربار CPU در ASan تقریباً 2 برابر، سربار اندازه کد بین 50٪ تا 2 برابر و سربار حافظه زیادی (بسته به الگوهای تخصیص شما، اما در حدود 2 برابر) است.
اندروید ۱۰ و آخرین نسخه AOSP در AArch64 از Hardware-assisted AddressSanitizer (HWASan) پشتیبانی میکنند، ابزاری مشابه با سربار رم کمتر و طیف وسیعتری از باگهای شناساییشده. HWASan علاوه بر باگهای شناساییشده توسط ASan، استفاده از پشته را پس از بازگشت تشخیص میدهد.
HWASan سربار CPU و اندازه کد مشابهی دارد، اما سربار RAM بسیار کمتری (15%) دارد. HWASan غیرقطعی است. فقط 256 مقدار برچسب ممکن وجود دارد، بنابراین احتمال از دست رفتن هر اشکالی 0.4% است. HWASan مناطق قرمز با اندازه محدود ASan برای تشخیص سرریزها و قرنطینه با ظرفیت محدود برای تشخیص استفاده پس از آزادسازی را ندارد، بنابراین برای HWASan مهم نیست که سرریز چقدر بزرگ باشد یا حافظه چه مدت پیش آزاد شده باشد. این باعث میشود HWASan بهتر از ASan باشد. میتوانید درباره طراحی HWASan یا درباره استفاده از HWASan در اندروید بیشتر بخوانید.
ASan علاوه بر سرریزهای هیپ، سرریزهای پشته/سراسری را نیز تشخیص میدهد و با حداقل سربار حافظه، سریع است.
این سند نحوه ساخت و اجرای بخشها/تمام اندروید با ASan را شرح میدهد. اگر در حال ساخت یک برنامه SDK/NDK با ASan هستید، به جای آن به Address Sanitizer مراجعه کنید.
فایلهای اجرایی تکی را با ASan ضدعفونی کنید
برای فایل اجرایی، LOCAL_SANITIZE:=address یا sanitize: { address: true } را به قانون ساخت اضافه کنید. میتوانید کد را برای مثالهای موجود جستجو کنید یا سایر ضدعفونیکنندههای موجود را پیدا کنید.
وقتی یک باگ شناسایی میشود، ASan یک گزارش مفصل هم در خروجی استاندارد و هم در logcat چاپ میکند و سپس فرآیند را متوقف میکند.
پاکسازی کتابخانههای اشتراکی با ASan
با توجه به نحوهی عملکرد ASan، کتابخانهای که با ASan ساخته شده باشد، فقط میتواند توسط یک فایل اجرایی که با ASan ساخته شده است، مورد استفاده قرار گیرد.
برای پاکسازی یک کتابخانه مشترک که در چندین فایل اجرایی استفاده میشود، که همه آنها با ASan ساخته نشدهاند، به دو نسخه از کتابخانه نیاز دارید. روش پیشنهادی برای انجام این کار، اضافه کردن کد زیر به Android.mk برای ماژول مورد نظر است:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
این دستور، کتابخانه را به جای /system/lib/asan در /system/lib /asan قرار میدهد. سپس، فایل اجرایی خود را با دستور زیر اجرا کنید:
LD_LIBRARY_PATH=/system/lib/asan
برای سرویسهای سیستم، موارد زیر را به بخش مناسب از /init.rc یا /init.$device$.rc اضافه کنید.
setenv LD_LIBRARY_PATH /system/lib/asan
با خواندن /proc/$PID/maps تأیید کنید که فرآیند از کتابخانههای موجود در /system/lib/asan استفاده میکند. اگر اینطور نیست، ممکن است لازم باشد SELinux را غیرفعال کنید:
adb rootadb shell setenforce 0# restart the process with adb shell kill $PID # if it is a system service, or may be adb shell stop; adb shell start.
ردیابی بهتر پشتهها
ASan از یک unwinder سریع و مبتنی بر اشارهگر فریم برای ثبت رد پشته برای هر رویداد تخصیص و آزادسازی حافظه در برنامه استفاده میکند. بیشتر اندروید بدون اشارهگر فریم ساخته شده است. در نتیجه، اغلب فقط یک یا دو فریم معنیدار دریافت میکنید. برای رفع این مشکل، یا کتابخانه را با ASan (توصیه میشود!) بازسازی کنید، یا با:
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
یا در محیط پردازش، ASAN_OPTIONS=fast_unwind_on_malloc=0 را تنظیم کنید. مورد دوم بسته به بار کاری میتواند بسیار CPU را درگیر کند.
نمادسازی
در ابتدا، گزارشهای ASan حاوی ارجاعاتی به آفستها در فایلهای باینری و کتابخانههای مشترک هستند. دو راه برای به دست آوردن اطلاعات فایل منبع و خط وجود دارد:
- مطمئن شوید که فایل باینری
llvm-symbolizerدر/system/binموجود است.llvm-symbolizerاز منابع موجود درthird_party/llvm/tools/llvm-symbolizerساخته شده است. - گزارش را از طریق اسکریپت
external/compiler-rt/lib/asan/scripts/symbolize.pyفیلتر کنید.
رویکرد دوم به دلیل در دسترس بودن کتابخانههای نمادین روی میزبان، میتواند دادههای بیشتری (یعنی مکانهای file:line ) ارائه دهد.
ASan در برنامهها
ASan نمیتواند کد جاوا را ببیند، اما میتواند اشکالات موجود در کتابخانههای JNI را تشخیص دهد. برای این کار، باید فایل اجرایی را با ASan بسازید که در این مورد /system/bin/app_process( 32|64 ) است. این کار ASan را به طور همزمان در تمام برنامههای روی دستگاه فعال میکند که بار سنگینی است، اما دستگاهی با 2 گیگابایت رم باید بتواند این کار را انجام دهد.
LOCAL_SANITIZE:=address به دستور ساخت app_process در frameworks/base/cmds/app_process اضافه کنید.
بخش service zygote از فایل system/core/rootdir/init.zygote( 32|64 ).rc مربوطه را ویرایش کنید تا خطوط زیر به بلوک خطوط تورفتگیدار حاوی class main که به همان میزان تورفتگی دارند، اضافه شوند:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
setenv ASAN_OPTIONS allow_user_segv_handler=true
ساخت، همگامسازی adb، بوت سریع با فلش و راهاندازی مجدد.
از ویژگی wrap استفاده کنید
رویکرد بخش قبلی، ASan را در هر برنامهای در سیستم قرار میدهد (در واقع، در هر نسل از فرآیند Zygote). میتوان فقط یک (یا چند) برنامه را با ASan اجرا کرد و مقداری از سربار حافظه را برای شروع کندتر برنامه مبادله کرد.
این کار را میتوان با شروع برنامه خود با ویژگی wrap. انجام داد. مثال زیر برنامه Gmail را تحت ASan اجرا میکند:
adb rootadb shell setenforce 0 # disable SELinuxadb shell setprop wrap.com.google.android.gm "asanwrapper"
در این زمینه، asanwrapper فایل /system/bin/app_process را به /system/bin/asan/app_process که با ASan ساخته شده است، بازنویسی میکند. همچنین /system/lib/asan را در ابتدای مسیر جستجوی کتابخانه پویا اضافه میکند. به این ترتیب، کتابخانههای ابزارسازی شده توسط ASan از /system/lib/asan هنگام اجرا با asanwrapper ، نسبت به کتابخانههای معمولی در /system/lib ترجیح داده میشوند.
اگر اشکالی پیدا شود، برنامه از کار میافتد و گزارش در لاگ چاپ میشود.
ضدعفونی کردن_هدف
اندروید ۷.۰ و بالاتر از ساخت کل پلتفرم اندروید با ASan به صورت همزمان پشتیبانی میکند. (اگر در حال ساخت نسخهای بالاتر از اندروید ۹ هستید، HWASan انتخاب بهتری است.)
دستورات زیر را در همان درخت ساخت اجرا کنید.
make -j42SANITIZE_TARGET=address make -j42
در این حالت، userdata.img شامل کتابخانههای اضافی است و باید به دستگاه نیز فلش شود. از خط فرمان زیر استفاده کنید:
fastboot flash userdata && fastboot flashall
این دستور دو مجموعه کتابخانه مشترک میسازد: نرمال در /system/lib (اولین فراخوانی make) و ASan-instrumented در /data/asan/lib (دومین فراخوانی make). فایلهای اجرایی از ساخت دوم، فایلهای اجرایی از ساخت اول را بازنویسی میکنند. فایلهای اجرایی ASan-instrumented با استفاده از /system/bin/linker_asan در PT_INTERP ، مسیر جستجوی کتابخانه متفاوتی دریافت میکنند که شامل /data/asan/lib قبل از /system/lib میشود.
سیستم ساخت، هنگامی که مقدار $SANITIZE_TARGET تغییر میکند، دایرکتوریهای شیء میانی را اشغال میکند. این امر باعث میشود که تمام اهداف بازسازی شوند و در عین حال فایلهای باینری نصب شده در /system/lib حفظ شوند.
برخی از اهداف را نمیتوان با ASan ساخت:
- فایلهای اجرایی با پیوند استاتیک
-
LOCAL_CLANG:=false -
LOCAL_SANITIZE:=falseبرایSANITIZE_TARGET=addressمناسب نیستند.
فایلهای اجرایی مانند این در ساخت SANITIZE_TARGET نادیده گرفته میشوند و نسخهای از اولین فراخوانی make در /system/bin باقی میماند.
کتابخانههایی مانند این بدون ASan ساخته میشوند. آنها میتوانند شامل مقداری کد ASan از کتابخانههای استاتیکی باشند که به آنها وابسته هستند.