1. مقدمه
کلمه سنیتایزر (sanitizer) در زبان انگلیسی به معنی ضدعفونی کننده یا پاککننده است، اما در حوزه کامپیوتر به ویژه در اکوسیستمهای LLVM و Clang معنایی کاملاً تخصصی پیدا میکند. در این بستر، sanitizer ابزاری است برای تشخیص خطاهای زمان اجرا بهوسیله ابزاردهی (instrumentation)؛ بهعبارت دیگر، سنیتایزرِ کد (Code Sanitizer) ابزاری در برنامهنویسی است که با تزریق کد ابزاردهی توسط کامپایلر در زمان اجرا، باگهایی را که بهصورت رفتارهای تعریفنشده یا مشکوک بروز میکنند شناسایی میکند. این رده از ابزارها نخستینبار با AddressSanitizer (ASan) محصول گوگل در سال ۲۰۱۲ معرفی شد؛ ابزاری که از حافظه سایه (shadow memory) با نگاشت مستقیم بهره میگیرد تا انواع فساد حافظه مانند سرریز بافر یا دسترسی به اشارهگر معلق (dangling pointer) را تشخیص دهد. ازاین رو در این سند از واژه سنیتایزر را استفاده میکنیم.
سنیتایزرهای LLVM مجموعهای از ابزارهای خطایابی هستند که برای شناسایی انواع مختلف باگها در کدهای C و ++C بهکار میروند. این مجموعه شامل ابزارهای متعددی از جمله AddressSanitizer، LeakSanitizer، ThreadSanitizer و MemorySanitizer است که هر یک قابلیت خطایابی ویژهای را فراهم میکنند.
یک سنیتایزر از یک ماژول ابزاردهی (instrumentation) در زمان کامپایل و یک کتابخانه زمان اجرا تشکیل میشود. برای استفاده از یک سنیتایزر، ابتدا باید اجراپذیرِ ابزاردهیشده مخصوص آن را با تعیین یک فلگ در هنگام کامپایل بسازید. هنگامی که این اجراپذیرِ ابزاردهیشده اجرا میشود، کتابخانه زمان اجرا عملیات مرتبط را رهگیری کرده و آنها را بررسی میکند. در صورت شناسایی یک مشکل، پیام هشداری تولید خواهد شد.
بهدلیل ابزاردهی و نحوه انجام فرایند خطایابی، ممکن است میزان استفاده از حافظه چندین برابر افزایش یابد و برنامه ابزاردهیشده چندین برابر کندتر اجرا شود. بنابراین، پس از پایان فرایند خطایابی، بازسازی مجدد کد بدون ابزاردهی از اهمیت بالایی برخوردار است.
2. ابزاردهی (Instrumentation) چیست؟
ابزاردهی به فرایند افزودن کد به یک برنامه گفته میشود تا بتوان وضعیت درونی آن را مشاهده و تحلیل کرد. برنامههای ابزاردهیشده رفتار کد را هنگام پاسخگویی به درخواستهای فعال اندازهگیری میکنند و دادههایی نظیر شاخصها (metrics)، رویدادها (events)، گزارشها (logs) و ردیابیها (traces) که بهاختصار MELT نامیده میشوند جمعآوری میکنند. بنابراین، هنگامی که برنامه را بهطور کامل ابزاردهی میکنید، تعیین میکنید که گزارشها چگونه پایش شوند و همچنین نحوه مشاهده سایر دادههایی را مشخص میکنید که میتوانند اطلاعات بیشتری درباره سامانه در اختیار شما قرار دهند.
در مقابلِ یک برنامه ابزاردهی نشده که تنها از گزارشهای لحظهای استفاده میکند، یک برنامه ابزاردهی شده تا حد ممکن اطلاعات مربوط به عملیات و رفتار سرویس را ردیابی میکند. این رویکرد جزئیات بیشتری درباره رخدادها ارائه میدهد و امکان مشاهده ارتباط میان درخواستها را فراهم میکند.
چند نمونه معروف از پلتفرم های توسعه ابزار دهی Valgrind، DynamoRIO، Intel-pin است.
3. ابزاردهی دقیقاً چهکار میکند؟
وقتی یک برنامه را ابزاردهی میکنیم، یعنی:
- در نقاط مختلف برنامه کد اضافی (instrumentation code) تزریق یا فعال میشود.
- این کد اضافی اطلاعات زمان اجرا را جمع آوری میکند، مانند:
- خطاهای حافظه
- رویدادها
- مقادیر متغیرها
- مسیرهای اجرای برنامه
- گزارشها (logs)
- ردیابیها (traces)
- شاخصها (metrics)
به همین دلیل است که Sanitizerها یا Profilerها فقط با ابزاردهی کارمیکنند.
4. Profiler دقیقاً چهکار میکند؟
Profiler هنگام اجرای برنامه با اندازهگیری زمان اجرای هر تابع، شمارش تعداد فراخوانیها، ثبت مصرف CPU، سنجش مصرف حافظه، تحلیل مسیرهای اجرا و شناسایی بخشهای کند کد، اطلاعات دقیقی برای بهینهسازی سرعت نرمافزار در اختیار برنامهنویس قرار میدهد.
5. تفاوت profiler و sanitizer
ابزار | کار اصلی | کاربرد |
Sanitizer | پیدا کردن باگهای خطرناک زمان اجرا | تشخیص خطاهای حافظه، Race، Undefined Behavior |
Profiler | اندازهگیری عملکرد و سرعت برنامه | بهینهسازی سرعت، پیدا کردن نقاط کُند |
6. کامپایلرهای پشتیبانی شده
این ابزارها تنها محدود به کامپایلرهای LLVM نیستند؛ بلکه با تمامی کامپایلرهای موجود بر روی سامانه Perlmutter بهجز کامپایلر Nvidia سازگار هستند. برای استفاده از این ابزارها نیازی به تغییر شیوه کامپایل کدهای MPI وجود ندارد (برای مثال، همچنان میتوانید از پوششدهندههای کامپایلر Cray مانند cc/CC بهصورت معمول استفاده کنید). برای کدهای غیر-MPI نیز میتوان از کامپایلرهای پایه C++/C زیر بهره برد.
GNU | Cray | Intel | AOCC | LLVM |
++gcc/g | craycc/craycxx | icx/icpx | ++clang/clang | ++clang/clang |
توجه داشته باشید که کامپایلرهای icc و icpc متعلق به Intel با ابزارهای سنیتایزر سازگار نیستند، زیرا مبتنی بر Clang نیستند.
7. فلگهای Sanitizer
این کامپایلرها بسیاری از فلگهای کامپایل مخصوص سنیتایزرهای LLVM را میپذیرند. شما میتوانید بسته به نیاز خود از آنها استفاده کنید. برای نمونه، لازم نیست کل کد را ابزاردهی کنید؛ بلکه میتوانید با استفاده از گزینههای -fsanitize-blacklist= یا -fsanitize-ignorelist= برخی توابع یا فایلهای مبدأ را از ابزاردهی مستثنا نمایید.
رفتار زمان اجرای یک ابزار با تنظیم متغیرهای محیطی مربوط به سنیتایزر و مقداردهی آنها با فلگهای زمان اجرا کنترل میشود. این متغیرها شامل ASAN_OPTIONS برای AddressSanitizer، LSAN_OPTIONS برای LeakSanitizer، TSAN_OPTIONS برای ThreadSanitizer، MSAN_OPTIONS برای MemorySanitizer و موارد مشابه هستند.
میتوانید فهرست فلگهای کامپایل و زمان اجرا را در صفحات زیر بیابید:
AddressSanitizer Flags[1]
ThreadSanitizer Flags[2]
Sanitizer Common Flags[3]
در ادامه، نحوه استفاده از سنیتایزرها را نشان میدهیم.
8. آشنایی با AddressSanitizer (معروف به Asan)
AddressSanitizer یک ابزار سریع برای تشخیص خطاهای حافظه است. این ابزار از یک ماژول ابزاردهیِ کامپایلر و یک کتابخانه زمان اجرا تشکیل میشود. AddressSanitizer میتواند انواع زیر از باگها را شناسایی کند:
- دسترسیهای خارج از محدوده (Out-of-bounds) به حافظه heap، stack و متغیرهای global
- استفاده پس از آزادسازی حافظه (Use-after-free)
- استفاده پس از بازگشت از تابع (Use-after-return
(clang flag -fsanitize-address-use-after-return=(never|runtime|always) default: runtime)
فعال سازی آن با دستور زیر که در لینوکس از پیش فعال است.
ASAN_OPTIONS=detect_stack_use_after_return=1
غیرفعال سازی با:
ASAN_OPTIONS=detect_stack_use_after_return=0
- استفاده پس از خروج از محدوده اسکوپ (Use-after-scope)
فلگ Clang:
-fsanitize-address-use-after-scope
- آزادسازی دوباره حافظه (Double-free) و آزادسازی نامعتبر (Invalid free)
- نشت حافظه (Memory leaks) — در حالت آزمایشی[4]
AddressSanitizer معمولاً حدود ۲ برابر کندی در اجرای برنامه ایجاد میکند.
8.1 نحوه ساخت
ساخت LLVM/Clang با CMake و فعال کردن زمان اجراي compiler-rt. يک نمونه پيكربندي CMake كه امكان استفاده و آزمون AddressSanitizer را فراهم ميكند:
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_RUNTIMES="compiler-rt" /llvm
لطفاً توجه كنيد كه اين پيكربندي پروژه Clang و زمان اجراي compiler-rt را فعال ميكند تا ابزار AddressSanitizer در فرآيند ساخت در دسترس باشد.
8.2 كاربرد
كافي است برنامه خود را با گزينه -fsanitize=address كامپايل و لينك كنيد. كتابخانه زمان اجراي AddressSanitizer بايد به فايل اجرايي نهايي لينك شود، بنابراين مطمئن باشيد كه براي گام نهايي لينك از clang استفاده ميكنيد نه ld. هنگام لينك كتابخانه هاي اشتراكي، زمان اجراي AddressSanitizer لينك نميشود، در نتيجه استفاده از -Wl,-z,defs ممكن است خطاي لينك ايجاد كند (اين گزينه را با AddressSanitizer به كار نبريد). براي كارايي قابل قبول، -O1 يا بالاتر اضافه كنيد. براي دريافت پشته هاي خواناتر در پيام هاي خطا از -fno-omit-frame-pointer استفاده كنيد. براي پشته هاي كاملا دقيق ممكن است نياز باشد درون خطي سازي را غيرفعال كنيد (فقط -O1 به كار بريد) و حذف فراخواني هاي دم مثل -fno-optimize-sibling-calls را غيرفعال كنيد.
cat example_UseAfterFree.cc
int main(int argc, char argv) {
int array = new int[100];
delete [] array;
return array[argc]; // BOOM
}
Compile and link
clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer example_UseAfterFree.cc
و یا
Compile
clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -c example_UseAfterFree.cc
Link
clang++ -g -fsanitize=address example_UseAfterFree.o
اگر يك خطا شناسايي شود، برنامه يك پيام خطا را در خروجي stderr چاپ ميكند و با كد خروجي غيرصفر خاتمه ميدهد. AddressSanitizer در نخستين خطاي كشف شده برنامه را متوقف ميكند. اين رفتار بر اساس طراحي است:
- اين رويكرد امكان توليد كد سريعتر و كوچكتر را فراهم ميكند (حدود پنج درصد در هر دو مورد).
- رفع خطاها اجتناب ناپذير ميشود. AddressSanitizer هشدار نادرست توليد نميكند. پس از وقوع خرابي حافظه، برنامه در وضعيت ناسازگار قرار ميگيرد كه ميتواند به نتايج گمراه كننده و گزارشهاي نادرست پس از آن منجر شود.
اگر فرآيند شما sandbox شده باشد و روي سيستم عامل OS X 10.10 يا نسخه هاي پيش از آن اجرا شويد، بايد متغير محيطي DYLD_INSERT_LIBRARIES را تنظيم كنيد و آن را به كتابخانه ASan كه همراه كامپايلري كه فايل اجرايي را ساخته است ارائه ميشود اشاره دهيد. (ميتوانيد كتابخانه را با جستجوي كتابخانه هاي پوياي داراي واژه asan در نام آنها پيدا كنيد.) اگر اين متغير محيطي تنظيم نشود، فرآيند تلاش ميكند دوباره اجرا شود. همچنين توجه داشته باشيد كه در صورت انتقال فايل اجرايي به ماشين ديگر، كتابخانه ASan نيز بايد منتقل شود.
8.3 نمادگذاری گزارشها
براي اينكه AddressSanitizer خروجي خود را نمادگذاري كند، بايد متغير محيطي ASAN_SYMBOLIZER_PATH را به مسير اجراي برنامه llvm-symbolizer اشاره دهيد (يا اطمينان داشته باشيد كه llvm-symbolizer در مسير $PATH شما قرار دارد).
ASAN_SYMBOLIZER_PATH=/usr/local/bin/llvm-symbolizer ./a.out
==9442== ERROR: AddressSanitizer heap-use-after-free on address 0x7f7ddab8c084 at pc 0x403c8c bp 0x7fff87fb82d0 sp 0x7fff87fb82c8
READ of size 4 at 0x7f7ddab8c084 thread T0
0 0x403c8c in main example_UseAfterFree.cc:4
1 0x7f7ddabcac4d in __libc_start_main ??:0
0x7f7ddab8c084 is located 4 bytes inside of 400-byte region [0x7f7ddab8c080,0x7f7ddab8c210)
freed by thread T0 here:
0 0x404704 in operator delete[](void) ??:0
1 0x403c53 in main example_UseAfterFree.cc:4
2 0x7f7ddabcac4d in __libc_start_main ??:0
previously allocated by thread T0 here:
0 0x404544 in operator new[](unsigned long) ??:0
1 0x403c43 in main example_UseAfterFree.cc:2
2 0x7f7ddabcac4d in __libc_start_main ??:0
==9442== ABORTING
اگر اين روش براي شما كار نميكند (مثلاً اگر فرآيند شما sandbox شده است)، ميتوانيد از يك اسكريپت جداگانه براي نمادگذاري خروجي به صورت آفلاين استفاده كنيد. نمادگذاري آنلاين را ميتوان با تنظيم متغير محيطي ASAN_OPTIONS=symbolize=0 به طور كامل غيرفعال كرد.
ASAN_OPTIONS=symbolize=0 ./a.out 2> log
projects/compiler-rt/lib/asan/scripts/asan_symbolize.py / < log | c++filt
==9442== ERROR: AddressSanitizer heap-use-after-free on address 0x7f7ddab8c084 at pc 0x403c8c bp 0x7fff87fb82d0 sp 0x7fff87fb82c8
READ of size 4 at 0x7f7ddab8c084 thread T0
0 0x403c8c in main example_UseAfterFree.cc:4
1 0x7f7ddabcac4d in __libc_start_main ??:0
...
نکته داشته باشيد که روي macOS ممکن است نياز باشد روي فايل اجرايي خود دستور dsymutil را اجرا کنيد تا اطلاعات فايل:خط در گزارشهاي AddressSanitizer در دسترس باشد.
8.4 بررسيهاي افزوده
8.4.1 بررسي ترتيب آغازينسازي
AddressSanitizer ميتواند به شکل اختياري مشکلات ترتيب آغازين سازي پويا را شناسايي کند؛ موقعي که آغازين سازي سراسري هاي تعريف شده در يک واحد ترجمه از سراسري هاي تعريف شده در واحد ترجمه ديگر استفاده ميکند. براي فعال کردن اين بررسي در زمان اجرا بايد متغير محيطي ASAN_OPTIONS=check_initialization_order=1 را تنظيم کنيد.
توجه داشته باشيد که اين گزينه روي macOS پشتيباني نميشود.
8.4.2 استفاده از پشته پس از بازگشت (UAR)
AddressSanitizer ميتواند به شکل اختياري مشکلات استفاده از پشته پس از بازگشت را شناسايي کند. اين قابليت به طور پيش فرض در دسترس است يا به صورت صريح با -fsanitize-address-use-after-return=runtime. براي غيرفعال کردن اين بررسي در زمان اجرا، متغير محيطي ASAN_OPTIONS=detect_stack_use_after_return=0 را تنظيم کنيد.
فعال کردن اين بررسي با -fsanitize-address-use-after-return=always باعث کاهش اندازه کد ميشود. با حذف کامل اين بررسي (-fsanitize-address-use-after-return=never) اندازه کد ميتواند باز هم کمتر شود.
خلاصه: -fsanitize-address-use-after-return=<mode>
- never: شناسايي خطاهاي UAR را به طور کامل غيرفعال ميکند (کاهش اندازه کد).
- runtime: کد شناسايي افزوده ميشود، اما ميتوان آن را از طريق محيط اجرا غيرفعال کرد (ASAN_OPTIONS=detect_stack_use_after_return=0).
- always: شناسايي خطاهاي UAR در همه حالات فعال است (کاهش اندازه کد، ولي نه به اندازه never).
8.4.3 شناسايي نشت حافظه
براي اطلاعات بيشتر درباره شناسايي كننده نشت در AddressSanitizer به LeakSanitizer مراجعه كنيد. شناسايي نشت به طور پيش فرض روي Linux فعال است و روي macOS ميتوان آن را با ASAN_OPTIONS=detect_leaks=1 فعال كرد. اين قابليت هنوز روي ساير سكوها پشتيباني نميشود.
8.5 مهار خطا
انتظار نمي رود AddressSanitizer هشدار نادرست ايجاد كند. اگر موردي مشاهده كرديد، دوباره بررسي كنيد؛ بسيار محتمل است كه هشدار دريافتي درست باشد!
8.5.1 مهار گزارشها در كتابخانههاي خارجي
جايگزيني زمان اجرا اين امكان را براي AddressSanitizer فراهم ميكند كه خطاها را در كدي كه دوباره كامپايل نشده است نيز بيابد. اگر در كتابخانه هاي خارجي با مشكلي روبه رو شديد، توصيه ميشود بلافاصله آن را به نگهدارنده آن كتابخانه گزارش كنيد تا برطرف شود. با اين حال، براي رفع مسدودي و ادامه آزمون ميتوانيد از سازوكار مهار زير استفاده كنيد. اين سازوكار فقط بايد براي مهار مشكلات موجود در كد خارجي به كار رود؛ روي كدي كه با AddressSanitizer دوباره كامپايل شده باشد كار نميكند.
براي مهار خطاها در كتابخانه هاي خارجي، متغير محيطي ASAN_OPTIONS را تنظيم كنيد تا به يك فايل مهار (suppression file) اشاره كند. ميتوانيد مسير كامل فايل يا مسير نسبي آن نسبت به محل فايل اجرايي خود را مشخص كنيد:
ASAN_OPTIONS=suppressions=MyASan.supp
از قالب زير براي مشخص كردن نام تابعها يا كتابخانه هايي كه ميخواهيد مهار كنيد استفاده كنيد. اين نامها را ميتوانيد در گزارش خطا مشاهده كنيد. به ياد داشته باشيد كه هرچه گستره مهار محدودتر باشد، باگهاي بيشتري قابل شناسايي خواهند بود.
interceptor_via_fun:NameOfCFunctionToSuppress
interceptor_via_fun:-[ClassName objCMethodToSuppress:]
interceptor_via_lib:NameOfTheLibraryToSuppress
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
// code that builds only under AddressSanitizer
# endif
#endif
8.5.3 غيرفعال كردن ابزارگذاري با __attribute__((no_sanitize(“address”)))
برخي بخشهاي كد نبايد توسط AddressSanitizer ابزارگذاري شوند. براي غيرفعال كردن ابزارگذاري يك تابع ميتوان از __attribute__((no_sanitize(“address”))) استفاده كرد (اين ويژگي داراي مترادفهاي منسوخ no_sanitize_address و no_address_safety_analysis است). ممكن است اين ويژگي توسط ديگر كامپايلرها پشتيباني نشود، بنابراين توصيه ميشود آن را همراه با __has_feature(address_sanitizer) استفاده كنيد.
به كار بردن همين ويژگي بر يك متغير سراسري باعث ميشود AddressSanitizer منطقههاي حفاظتي را پيرامون آن اضافه نكند و دسترسي خارج از مرز مربوط به آن را تشخيص ندهد.
AddressSanitizer همچنين __attribute__((disable_sanitizer_instrumentation)) را پشتيباني ميكند. عملكرد اين ويژگي مشابه __attribute__((no_sanitize(“address”))) است، ولي علاوه بر آن ابزارگذاري انجام شده توسط ساير sanitizer ها را نيز غيرفعال ميكند.
8.5.4 مهار خطا در كد دوباره كامپايل شده (Ignorelist)
AddressSanitizer از انواع موجوديت src و fun در فهرست موارد خاص Sanitizer پشتيباني ميكند؛ اين موارد را ميتوان براي مهار گزارشهاي خطا در فايلهاي مبدأ يا تابعهاي مشخص به كار برد. علاوه بر اين، AddressSanitizer موجوديتهاي global و type را معرفي ميكند كه ميتوان براي مهار گزارشهاي مربوط به دسترسي خارج از مرز به متغيرهاي سراسري با نامها و نوعهاي مشخص (فقط نوعهاي class يا struct مجاز هستند) از آنها استفاده كرد.
همچنين ميتوانيد از دسته init براي مهار گزارشهاي مربوط به مشكلات ترتيب آغازين سازي كه در فايلهاي مبدأ يا متغيرهاي سراسري خاص رخ ميدهد استفاده كنيد.
# Suppress error reports for code in a file or in a function:
src:bad_file.cpp
# Ignore all functions with names containing MyFooBar:
fun:MyFooBar
# Disable out-of-bound checks for global:
global:bad_array
# Disable out-of-bound checks for global instances of a given class ...
type:Namespace::BadClassName
# ... or a given struct. Use wildcard to deal with anonymous namespace.
type:Namespace2::::BadStructName
# Disable initialization-order checks for globals:
global:bad_init_global=init
type:BadInitClassSubstring=init
src:bad/init/files/=init
8.5.5 مهار نشت حافظه
گزارشهاي نشت حافظه كه توسط [5]LeakSanitizer توليد ميشوند (اگر به عنوان بخشي از AddressSanitizer اجرا شود) را ميتوان با يك فايل جداگانه مهار كرد. اين فايل از طريق متغير محيطي زير معرفي ميشود:
LSAN_OPTIONS=suppressions=MyLSan.supp
این فايل بايد شامل خطهايي با قالب leak:<pattern> باشد. نشت حافظه در صورتي مهار ميشود كه pattern با هر يك از نام تابع، نام فايل مبدأ، يا نام كتابخانه موجود در پشته نمادگذاري شده گزارش نشت حافظه مطابقت داشته باشد. براي جزئيات بيشتر به مستند كامل مراجعه كنيد[6].
8.6 كنترل توليد كد
8.6.1 تفكيك ابزارگذاري از كد
AddressSanitizer به طور پيش فرض براي بهبود كارايي زمان اجرا، كد ابزارگذاري را درون خطي قرار ميدهد كه اين امر اندازه فايل دودويي را افزايش ميدهد. استفاده از گزينه -fsanitize-address-outline-instrumentation (پيش فرض: false) در clang باعث ميشود تمام ابزارگذاريها به صورت تفكيك شده درآيند كه اندازه كد توليدي را كاهش ميدهد، اما كارايي زمان اجرا را كمتر ميكند.
8.7 محدوديتها
AddressSanitizer نسبت به اجراي معمول حافظه واقعي بيشتري مصرف ميكند. ميزان سربار دقيق به اندازه تخصيصها بستگي دارد. هرچه تخصيصهاي شما كوچكتر باشد سربار بيشتر خواهد بود.
AddressSanitizer حافظه پشته بيشتري مصرف ميكند. افزايش تا سه برابر مشاهده شده است.
روي سكوهاي ٦٤ بيتي، AddressSanitizer فضاي نشاني مجازي ١٦ ترابايت يا بيشتر را نگاشت ميكند (اما رزرو نميكند). اين بدان معناست كه ابزارهايي مانند ulimit ممكن است مطابق انتظار معمولي عمل نكنند.
لينك ايستا براي فايل اجرايي پشتيباني نميشود.
8.8 ملاحظات امنيتي
AddressSanitizer يك ابزار كشف باگ است و زمان اجراي آن براي لينك شدن با اجرايي هاي توليدي در محيط واقعي طراحي نشده است. هرچند براي آزمون مفيد است، اما زمان اجراي آن با ملاحظات حساس به امنيت توسعه داده نشده و ممكن است امنيت فايل اجرايي حاصل را كاهش دهد.
8.9 پلتفرمهاي پشتيباني شده
AddressSanitizer روي پلتفرمهای زير پشتيباني ميشود:
- Linux
- macOS
- iOS Simulator
- Android
- NetBSD
- FreeBSD
- Windows 8.1+
8.10 وضعيت كنوني
AddressSanitizer از LLVM 3.1 به بعد روي پلتفرمهای پشتيباني شده كاملاً كاربردي است. مجموعه آزمون آن در فرآيند ساخت CMake يكپارچه شده و با دستور make check-asan قابل اجرا است.
نسخه Windows كاربردي است و توسط Chrome و Firefox استفاده ميشود، اما به اندازه نسخههاي ديگر پشتيباني شده نيست.
8.11 اطلاعات بيشتر
https://github.com/google/sanitizers/wiki/AddressSanitizer
8.12 مروری بر AddressSanitizer با روایتی دیگر:
AddressSanitizer يك شناسايي كننده خطاهاي حافظه براي C و ++C است. اين ابزار ميتواند انواع زير از خطاها را شناسايي كند:
- استفاده پس از آزادسازي حافظه (Use after free)
- دسترسي خارج از مرز به آرايه ها در heap، stack و متغيرهاي سراسري (به ترتيب: سرريز/زيرريز بافر heap، سرريز/زيرريز بافر stack، سرريز/زيرريز بافر سراسري)
- استفاده پس از بازگشت (Use after return): استفاده از يك شيء پشته پس از خروج از تابع سازنده آن
- استفاده خارج از دامنه (Use after scope): استفاده از يك شيء پشته در بيرون از دامنه تعريف آن
- خطاهاي ترتيب آغازين سازي: نتيجه غيرقطعي به دليل ترتيب نامشخص اجراي سازنده هاي شيءهاي سراسري در فايلهاي مبدأ متفاوت
- آزادسازي دوباره يا آزادسازي نامعتبر (Double-free، Invalid free)
- نشت حافظه
براي ابزارگذاري با AddressSanitizer، كد خود را با گزينه -fsanitize=address كامپايل و لينك كنيد. در ادامه يك مثال در محيط PrgEnv-gnu آمده است:
$ cat illegalmemoryaccess.cpp
#include
int main(int argc, char argv) {
int array = new int[10];
for (int i = 0; i 0x0c087fff8000: fa fa 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
Right alloca redzone: cb
==2267569==ABORTING
اين ابزار از يك بلوك حافظه اضافي به نام shadow bytes براي پيگيري وضعيت بلوكهاي حافظه تخصيص يافته استفاده ميكند. به ازاي هر هشت بايت برنامه، يك shadow byte وجود دارد و نشاني shadow byte در سامانه هاي x86_64 مانند Perlmutter به صورت زير محاسبه ميشود:
Shadow = (Mem >> 3) + 0x7fff8000
در نتيجه، بلوك حافظه تخصيص يافته [0x604000000010, 0x604000000038) در مثال بالا به بازه [0xc087fff8002, 0xc087fff8007) نگاشت ميشود.
با استفاده از shadow byte ها، ابزار يك سرريز بافر heap به اندازه چهار بايت را به درستي تشخيص ميدهد. خط داراي پيكان در نقشه shadow bytes داراي پنج مقدار 00 است كه به معناي قابل دسترس بودن چهل بايت است (به راهنماي پايين نقشه توجه كنيد). shadow byte بعدي مربوط به بايتهاي موجود در heap left redzone است؛ ناحيه اي كه نبايد تغيير كند و با نماد fa مشخص شده است، كه نشان ميدهد دسترسي خارج از مرز در آنجا رخ داده است.
مثال بعدي شناسايي يك خطاي استفاده پس از آزادسازي حافظه (use after free) را نشان ميدهد:
$ cat accessafterdelete.cpp
#include
int main(int argc, char argv) {
int array = new int[100];
delete [] array;
return array[10]; // access after delete.
}
$ g++ -O1 -g -fsanitize=address -fno-omit-frame-pointer accessafterdelete.cpp
$ ./a.out
=================================================================
==2315893==ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000068 at pc 0x0000004009b6 bp 0x7ffc732d66f0 sp 0x7ffc732d66e8
READ of size 4 at 0x614000000068 thread T0
#0 0x4009b5 in main /pscratch/sd/e/elvis/addresssanitizer/accessafterdelete.cpp:7
#1 0x7f1e67a3c24c in __libc_start_main (/lib64/libc.so.6+0x3524c)
#2 0x4008b9 in _start ../sysdeps/x86_64/start.S:120
0x614000000068 is located 40 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)
freed by thread T0 here:
#0 0x7f1e686bc498 in operator delete[](void) (/usr/lib64/libasan.so.8+0xbc498)
#1 0x400983 in main /pscratch/sd/e/elvis/addresssanitizer/accessafterdelete.cpp:6
#2 0x7f1e67a3c24c in __libc_start_main (/lib64/libc.so.6+0x3524c)
previously allocated by thread T0 here:
#0 0x7f1e686bba88 in operator new[](unsigned long) (/usr/lib64/libasan.so.8+0xbba88)
#1 0x400978 in main /pscratch/sd/e/elvis/addresssanitizer/accessafterdelete.cpp:4
#2 0x7f1e67a3c24c in __libc_start_main (/lib64/libc.so.6+0x3524c)
SUMMARY: AddressSanitizer: heap-use-after-free /pscratch/sd/e/elvis/addresssanitizer/accessafterdelete.cpp:7 in main
Shadow bytes around the buggy address:
0x0c287fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c287fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c287fff8000: fa fa fa fa fa fa fa fa fd fd fd fd fd[fd]fd fd
0x0c287fff8010: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c287fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c287fff8030: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
0x0c287fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c287fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==2315893==ABORTING
توجه داشته باشيد كه ناحيه آزادشده با اندازه چهارصد بايت با fd علامتگذاري شده است و بلوك حافظه اي كه به اشتباه به آن دسترسي شده است با [fd] مشخص شده است.
9. آشنایی با ThreadSanitizer (معروف به TSan)
ThreadSanitizer ابزاري براي شناسايي رقابت داده اي (data races) است. اين ابزار از يك ماژول ابزارگذاري در كامپايلر و يك كتابخانه زمان اجرا تشكيل ميشود. كندي معمول ناشي از ThreadSanitizer حدود پانزده تا پنج برابر است. سربار حافظه معمول آن نيز حدود ده تا پنج برابر ميباشد.
9.1 روش ساخت
ساخت LLVM/Clang با CMake انجام ميشود.
9.2 پلتفرمهاي پشتيباني شده
ThreadSanitizer روي سامانه هاي زير پشتيباني ميشود:
- Android: aarch64، x86_64
- Darwin: arm64، x86_64
- FreeBSD
- Linux: aarch64، x86_64، powerpc64، powerpc64le
- NetBSD
پشتيباني براي ساير معماري هاي ٦٤ بيتي ممكن است و از مشاركت استقبال ميشود. پشتيباني براي پلتفرمهاي ٣٢ بيتي دشوار است و در برنامه قرار ندارد.
9.3 كاربرد
كافي است برنامه خود را با گزينه -fsanitize=thread كامپايل و لينك كنيد. براي كارايي مناسب، -O1 يا بالاتر را اضافه كنيد. براي دريافت نام فايل و شماره خط در هشدارها از -g استفاده كنيد.
برای مثال:
cat projects/compiler-rt/lib/tsan/lit_tests/tiny_race.c
#include
int Global;
void Thread1(void x) {
Global = 42;
return x;
}
int main() {
pthread_t t;
pthread_create(&t, NULL, Thread1, NULL);
Global = 43;
pthread_join(t, NULL);
return Global;
}
clang -fsanitize=thread -g -O1 tiny_race.c
اگر يك خطا شناسايي شود، برنامه يك پيام خطا در خروجي stderr چاپ ميكند. در حال حاضر ThreadSanitizer براي نمادگذاري خروجي خود از يك فرآيند خارجي addr2line استفاده ميكند (اين مورد در آينده اصلاح خواهد شد).
./a.out
WARNING: ThreadSanitizer: data race (pid=19219)
Write of size 4 at 0x7fcf47b21bc0 by thread T1:
#0 Thread1 tiny_race.c:4 (exe+0x00000000a360)
Previous write of size 4 at 0x7fcf47b21bc0 by main thread:
#0 main tiny_race.c:10 (exe+0x00000000a3b4)
Thread T1 (running) created at:
#0 pthread_create tsan_interceptors.cc:705 (exe+0x00000000c790)
#1 main tiny_race.c:9 (exe+0x00000000a3a4)
9.4 __has_feature(thread_sanitizer)
در برخي موارد ممكن است لازم باشد بسته به اينكه ThreadSanitizer فعال است يا خير، كد متفاوتي اجرا شود. براي اين منظور ميتوان از __has_feature استفاده كرد.
#if defined(__has_feature)
# if __has_feature(thread_sanitizer)
// code that builds only under ThreadSanitizer
# endif
#endif
9.5 __attribute__((no_sanitize(“thread”)))
برخي بخشهاي كد نبايد توسط ThreadSanitizer ابزارگذاري شوند. براي غيرفعال كردن ابزارگذاري بارگذاريها و ذخيره هاي ساده (غيراتميك) در يك تابع ميتوانيد از ويژگي no_sanitize(“thread”) استفاده كنيد. ThreadSanitizer همچنان اين تابع را ابزارگذاري ميكند تا از هشدارهاي نادرست جلوگيري كند و پشته هاي قابل اتكا ارائه دهد. ممكن است اين ويژگي توسط ساير كامپايلرها پشتيباني نشود، بنابراين توصيه ميشود آن را همراه با __has_feature(thread_sanitizer) استفاده كنيد.
9.6 __attribute__((disable_sanitizer_instrumentation))
اين ويژگي را ميتوان به تابعها اعمال كرد تا تمام انواع ابزارگذاري غيرفعال شوند. اين كار ممكن است به هشدارهاي نادرست و پشته هاي نادرست منجر شود. بنابراين تنها در موارد كاملاً ضروري بايد استفاده شود؛ براي نمونه براي كدي كه هيچگونه ابزارگذاري و آثار جانبي ناشي از آن را تحمل نميكند. اين ويژگي بر no_sanitize(“thread”) برتري دارد.
9.7 لیست نادیده گرفتن یا Ignorelist
ThreadSanitizer از موجوديتهاي src و fun در فهرست موارد خاص Sanitizer پشتيباني ميكند. اين موارد را ميتوان براي مهار گزارشهاي رقابت داده اي در فايلهاي مبدأ يا تابعهاي مشخص به كار برد. بر خلاف تابعهايي كه با no_sanitize(“thread”) علامتگذاري شده اند، تابعهاي موجود در ignorelist به طور كامل ابزارگذاري نميشوند. اين موضوع ميتواند به هشدارهاي نادرست منجر شود، به دليل از دست رفتن همزماني از طريق عمليات اتمي و حذف فريمهاي پشته در گزارشها.
9.8 محدوديتها
ThreadSanitizer نسبت به اجراي بومي حافظه واقعي بيشتري مصرف ميكند. در تنظيمات پيش فرض، سربار حافظه پنج برابر بعلاوه يك مگابايت براي هر رشته است. تنظيمات با سربار سه برابر (تحليل كم دقت تر) و نه برابر (تحليل دقيق تر) نيز موجود است.
ThreadSanitizer بخش زيادي از فضاى نشاني مجازي را نگاشت ميكند (اما رزرو نميكند). اين بدان معناست كه ابزارهايي مانند ulimit ممكن است مطابق انتظار معمول عمل نكنند.
لينك ايستا براي libc و libstdc++ پشتيباني نميشود.
فايلهاي اجرايي غيرقابل جابجايي (non-position-independent) پشتيباني نميشوند. بنابراين، استفاده از -fsanitize=thread باعث ميشود Clang در صورت نبود -fPIC طوري عمل كند كه گويي -fPIE داده شده است، و در مرحله لينك نيز طوري عمل كند كه گويي -pie داده شده است.
9.9 ملاحظات امنيتي
ThreadSanitizer يك ابزار كشف باگ است و زمان اجراي آن براي لينك شدن با فايلهاي اجرايي توليدي در محيط واقعي طراحي نشده است. اگرچه براي آزمون مفيد است، اما زمان اجراي آن با قيود حساس به امنيت توسعه داده نشده و ممكن است امنيت فايل اجرايي حاصل را كاهش دهد.
9.10 وضعيت كنوني
ThreadSanitizer در مرحله بتا قرار دارد. اين ابزار روي برنامه هاي بزرگ C++ كه از pthreads استفاده ميكنند كار ميكند، اما هنوز تضميني ارائه نميدهيم. نخ دهي C++11 با libc++ پشتيباني ميشود. مجموعه آزمون آن در فرآيند ساخت CMake يكپارچه شده و با دستور make check-tsan قابل اجرا است.
ما به طور فعال در حال بهبود اين ابزار هستيم — با ما همراه باشيد. هرگونه كمك، به ويژه به شكل آزمونهاي مستقل و كوچك، بسيار ارزشمند است.
9.11 اطلاعات بيشتر
https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual
9.12 مروری بر ThreadSanitizer با روایتی دیگر:
ThreadSanitizer (به اختصار TSan) رقابت داده اي ميان رشته ها را شناسايي ميكند.
براي ابزارگذاري با ThreadSanitizer، كد خود را با گزينه -fsanitize=thread كامپايل و لينك كنيد. براي دريافت نام فايل و شماره خط در خروجي، بايد -g را اضافه كنيد. براي كارايي بهتر، ميتوانيد -O1 يا سطح بالاتر را نيز بيافزاييد.
در ادامه نمونه اي از شناسايي يك رقابت داده اي ميان رشته هاي OpenMP در محيط PrgEnv-gnu آمده است:
$ cat buggyreduction_omp.c
#include
int main (int argc, char argv) {
int sum = 0;
#pragma omp parallel for shared(sum)
for (int i=0; i<1000; i++)
sum += i;
printf("sum = %d\n", sum);
return 0;
}
$ cc -fsanitize=thread -g -O1 -fopenmp buggyreduction_omp.c
$ export OMP_NUM_THREADS=8
$ ./a.out
=================
WARNING: ThreadSanitizer: data race (pid=2240264)
Read of size 4 at 0x7ffdf6e678bc by thread T1:
#0 main._omp_fn.0 /pscratch/sd/e/elvis/sanitizers/buggyreduction_omp.c:6 (a.out+0x400895)
#1 (libgomp.so.1+0x1dd4d)
Previous write of size 4 at 0x7ffdf6e678bc by main thread:
#0 main._omp_fn.0 /pscratch/sd/e/elvis/sanitizers/buggyreduction_omp.c:7 (a.out+0x4008aa)
#1 GOMP_parallel (libgomp.so.1+0x14e95)
Location is stack of main thread.
Location is global '' at 0x000000000000 ([stack]+0x1e8bc)
Thread T1 (tid=2240266, running) created by main thread at:
#0 pthread_create (libtsan.so.2+0x61be6)
#1 (libgomp.so.1+0x1e38f)
SUMMARY: ThreadSanitizer: data race /pscratch/sd/e/elvis/sanitizers/buggyreduction_omp.c:6 in main._omp_fn.0
==================
sum = 335625
ThreadSanitizer: reported 1 warnings
رفتار زمان اجرا با متغير محيطي TSAN_OPTIONS كنترل ميشود. براي اطلاع از پرچمهاي زمان اجرا كه ميتوان همراه آن استفاده كرد، به [۷]ThreadSanitizer Flags مراجعه كنيد.
ممکن است لازم باشد يك فايل اجراييِ ابزارگذاري شده را چند بار اجرا كنيد؛ زيرا اگر يك شرايط رقابتي در يك اجرا رخ ندهد، حتي اگر كد معيوب باشد، هيچ پيام هشداري نخواهيد ديد.
براي اطلاعات بيشتر درباره اين ابزار، به منابع زير مراجعه كنيد:
طبق يكي از صفحات ياد شده، هزينه شناسايي رقابت داده اي بسته به برنامه متفاوت است؛ اما براي يك برنامه معمول، مصرف حافظه ممكن است بين پنج تا ده برابر افزايش يابد و زمان اجرا دو تا بيست برابر بيشتر شود.
10. آشنایی با MemorySanitizer (معروف به MSan)
MemorySanitizer شناسايي كننده استفاده از حافظه مقدارندهي نشده است. اين ابزار از يك ماژول ابزارگذاري در كامپايلر و يك كتابخانه زمان اجرا تشكيل ميشود.
كندي معمول ايجاد شده توسط MemorySanitizer حدود سه برابر است.
در فهرست زير برخي از مواردي كه MemorySanitizer در آنها خطا گزارش ميكند آمده است (فهرست كامل نيست):
- استفاده از مقدار مقدارندهي نشده در يك شاخه شرطي
- استفاده از اشاره گر مقدارندهي نشده براي دسترسي به حافظه
- ارسال يا بازگرداندن مقدار مقدارندهي نشده از يك فراخواني تابع، كه رفتار نامعين محسوب ميشود. اين بررسي را ميتوان با -fno-sanitize-memory-param-retval غيرفعال كرد.
- ارسال داده مقدارندهي نشده به برخي فراخواني هاي libc
10.1 روش ساخت
ساخت LLVM/Clang با CMake[۱۰] انجام ميشود.
10.2 كاربرد
كافي است برنامه خود را با گزينه -fsanitize=memory كامپايل و لينك كنيد. كتابخانه زمان اجراي MemorySanitizer بايد به فايل اجرايي نهايي لينك شود، بنابراين در گام نهايي لينك حتماً از clang استفاده كنيد نه ld. هنگام لينك كتابخانه هاي اشتراكي، كتابخانه زمان اجراي MemorySanitizer لينك نميشود، بنابراين -Wl,-z,defs ممكن است خطاي لينك ايجاد كند (اين گزينه را همراه MemorySanitizer استفاده نكنيد). براي كارايي مناسب -O1 يا بالاتر اضافه كنيد. براي دريافت پشته هاي معنا دار در پيامهاي خطا از -fno-omit-frame-pointer استفاده كنيد. براي پشته هاي كاملاً دقيق ممكن است نياز باشد درون خطي سازي را غيرفعال كنيد (فقط -O1 را به كار ببريد) و حذف فراخواني ها را نيز غيرفعال كنيد (-fno-optimize-sibling-calls).
cat umr.cc
#include
int main(int argc, char argv) {
int a = new int[10];
a[5] = 0;
if (a[argc])
printf("xx\n");
return 0;
}
clang -fsanitize=memory -fno-omit-frame-pointer -g -O2 umr.cc
اگر خطايي شناسايي شود، برنامه يك پيام خطا در خروجي stderr چاپ ميكند و با يك كد خروجي غيرصفر خاتمه مييابد.
./a.out
WARNING: MemorySanitizer: use-of-uninitialized-value
0 0x7f45944b418a in main umr.cc:6
1 0x7f45938b676c in __libc_start_main libc-start.c:226
به طور پيش فرض، MemorySanitizer با نخستين خطاي كشف شده برنامه را متوقف ميكند. اگر گزارش خطا براي شما دشوار است، رديابي خاستگاه (origin tracking[۱۱]) را فعال كنيد.
10.2.1 __has_feature(memory_sanitizer)
در برخي موارد ممكن است لازم باشد بسته به فعال بودن يا نبودن MemorySanitizer كد متفاوتي اجرا شود. براي اين منظور ميتوان از [۱۲]__has_feature استفاده كرد.
#if defined(__has_feature)
# if __has_feature(memory_sanitizer)
// code that builds only under MemorySanitizer
# endif
#endif
10.2.2 __attribute__((no_sanitize(“memory”)))
برخي بخشهاي كد نبايد توسط MemorySanitizer بررسي شوند. براي غيرفعال كردن بررسي مقادير مقدارندهي نشده در يك تابع، ميتوانيد از ويژگي no_sanitize(“memory”) استفاده كنيد. MemorySanitizer ممكن است همچنان چنين تابعهايي را ابزارگذاري كند تا از هشدارهاي نادرست جلوگيري شود. ممكن است اين ويژگي توسط كامپايلرهاي ديگر پشتيباني نشود، بنابراين توصيه ميشود آن را همراه با __has_feature(memory_sanitizer) به كار ببريد.
10.2.3 __attribute__((disable_sanitizer_instrumentation))
اين ويژگي را ميتوان به تابعها اعمال كرد تا تمام انواع ابزارگذاري غيرفعال شوند. نتيجه اين كار ممكن است ايجاد هشدارهاي نادرست باشد، بنابراين تنها در مواقع كاملاً ضروري بايد استفاده شود؛ براي نمونه كدي كه هيچ ابزارگذاري و آثار جانبي مرتبط را تحمل نميكند. اين ويژگي بر no_sanitize(“memory”) برتري دارد.
10.2.4 Ignorelist
MemorySanitizer از موجوديتهاي src و fun در فهرست موارد خاص Sanitizer پشتيباني ميكند كه ميتوان از آنها براي كاهش سختگيري بررسي هاي MemorySanitizer در فايلهاي مبدأ يا تابعهاي مشخص استفاده كرد. با اين تنظيم، تمام هشدارهاي استفاده از مقدار مقدارندهي نشده مهار ميشوند و تمام مقاديري كه از حافظه بارگذاري شوند كاملاً مقدارده شده فرض ميگردند.
10.3 نمادگذاري گزارش
MemorySanitizer براي نمايش نام فايل و شماره خط در گزارش ها از يك نمادگذار خارجي استفاده ميكند. اطمينان يابيد كه برنامه llvm-symbolizer در مسير PATH شما باشد يا متغير محيطي MSAN_SYMBOLIZER_PATH را به مسير آن تنظيم كنيد.
10.4 رديابي خاستگاه (Origin Tracking)
MemorySanitizer ميتواند خاستگاه مقادير مقدارندهي نشده را مشابه گزينه –track-origins در Valgrind ردگيري كند. اين قابليت با گزينه -fsanitize-memory-track-origins=2 (يا به اختصار -fsanitize-memory-track-origins) در Clang فعال ميشود.
cat umr2.cc
#include
int main(int argc, char argv) {
int a = new int[10];
a[5] = 0;
volatile int b = a[argc];
if (b)
printf("xx\n");
return 0;
}
clang -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2 umr2.cc
./a.out
WARNING: MemorySanitizer: use-of-uninitialized-value
0 0x7f7893912f0b in main umr2.cc:7
1 0x7f789249b76c in __libc_start_main libc-start.c:226
Uninitialized value was stored to memory at
0 0x7f78938b5c25 in __msan_chain_origin msan.cc:484
1 0x7f7893912ecd in main umr2.cc:6
Uninitialized value was created by a heap allocation
0 0x7f7893901cbd in operator new[](unsigned long) msan_new_delete.cc:44
1 0x7f7893912e06 in main umr2.cc:4
به طور پيش فرض، MemorySanitizer هم نقطه هاي تخصيص و هم تمام ذخيره هاي ميانياي را كه يك مقدار مقدارندهي نشده از آنها عبور كرده است جمعآوري ميكند. رديابي خاستگاه در عمل براي اشكالزدايي گزارشهاي MemorySanitizer بسيار مفيد بوده است. اين قابليت زمان اجراي برنامه را حدود يك و نيم تا دو برابر بيشتر از كندي معمولي MemorySanitizer كند ميكند و سربار حافظه را افزايش ميدهد.
گزينه -fsanitize-memory-track-origins=1 در Clang حالت كمي سريعتري را فعال ميكند كه در آن MemorySanitizer تنها نقطه هاي تخصيص را جمعآوري ميكند و ذخيره هاي مياني را در نظر نميگيرد.
10.5 شناسايي استفاده پس از نابودي (Use-after-destruction)
MemorySanitizer قابليت شناسايي استفاده پس از نابودي را نيز شامل ميشود. پس از فراخواني تابع سازنده معكوس (destructor)، شيء ديگر قابل خواندن در نظر گرفته نميشود و استفاده از حافظه زيرين آن در زمان اجرا به گزارش خطا منجر ميشود. براي تعريف چرخه حيات(lifetime)، به استاندارد مراجعه كنيد[13].
اين قابليت را ميتوان با يكي از روشهاي زير غيرفعال كرد:
- افزودن گزينه -fno-sanitize-memory-use-after-dtor هنگام كامپايل
- تنظيم متغير محيطي MSAN_OPTIONS=poison_in_dtor=0 پيش از اجراي برنامه
10.6 پردازش كد خارجي
MemorySanitizer مستلزم آن است كه تمام كد برنامه ابزارگذاري شود. اين شامل همه كتابخانه هايي كه برنامه به آنها وابسته است (حتي libc) نيز ميشود. عدم تحقق اين موضوع ممكن است به هشدارهاي نادرست منجر شود. به همين دليل ممكن است لازم شود تمام كدهاي اسمبلی درون خطي كه در حافظه مينويسند با كد خالص C++/C جايگزين شوند.
دستيابي به ابزارگذاري كامل MemorySanitizer بسيار دشوار است. براي آسانتر كردن كار، كتابخانه زمان اجراي MemorySanitizer بيش از هفتاد نگهدارنده (interceptor) براي رايجترين توابع libc دارد. اين نگهدارنده ها باعث ميشوند بتوان برنامه هاي ابزارگذاري شده را همراه libc ابزارگذاري نشده اجرا كرد. براي نمونه، توسعه دهندگان توانستند كامپايلر Clang ابزارگذاري شده با MemorySanitizer را با لينك كردن آن با ++libc ابزارگذاري شده (به عنوان جايگزين ++libstdc) بسازند.
10.7 ملاحظات امنيتي
MemorySanitizer يك ابزار كشف باگ است و زمان اجراي آن براي لينك شدن با فايلهاي اجرايي توليدي در محيط واقعي طراحي نشده است. هرچند براي آزمون سودمند است، اما زمان اجراي آن با قيود حساس به امنيت توسعه داده نشده و ممكن است امنيت فايل اجرايي را كاهش دهد.
10.8 سكوهاي پشتيباني شده
MemorySanitizer روي سامانههاي زير پشتيباني ميشود:
- Linux
- NetBSD
- FreeBSD
10.9 محدوديتها
- MemorySanitizer نسبت به اجراي بومي دو برابر حافظه واقعي مصرف ميكند؛ با رديابي خاستگاه، سه برابر.
- MemorySanitizer فضاي نشاني مجازي ٦٤ ترابايت را نگاشت ميكند (اما رزرو نميكند). اين بدان معناست كه ابزارهايي مانند ulimit ممكن است مطابق انتظار معمول عمل نكنند.
- لينك ايستا پشتيباني نميشود.
- نسخه هاي قديمي تر (LLVM 3.7 و پيش از آن) با اجرايي هاي غيرقابل جابجايي سازگار نبودند و ممكن بود روي نسخه هايي از هسته Linux با ASLR غيرفعال دچار خطا شوند. براي جزئيات به مستند نسخه هاي قديمي مراجعه كنيد.
- MemorySanitizer ممكن است با اجرايي هاي قابل جابجايي (PIE) در FreeBSD 13 سازگار نباشد، اما يك بررسي زمان اجرا وجود دارد كه در اين حالت هشدار ميدهد.
10.10 وضعيت كنوني
MemorySanitizer روي برنامه هاي بزرگ واقعي (از جمله خود Clang/LLVM) كه ميتوانند از منبع دوباره كامپايل شوند شامل تمام كتابخانه هاي وابسته به خوبي كار ميكند.
10.11 اطلاعات بيشتر
https://github.com/google/sanitizers/wiki/MemorySanitizer
10.12 مروری بر MemorySanitizer:
MemorySanitizer (MSan) هنگامي هشدار ميدهد كه يك متغير مقدارندهي نشده خوانده شود.
براي ابزارگذاري با MemorySanitizer، كد خود را با گزينه -fsanitize=memory كامپايل و لينك كنيد. براي دريافت نام فايل و شماره خط در خروجي بايد -g را اضافه كنيد، اما براي كارايي بهتر از سطح بهينه سازي -O1 يا بالاتر استفاده كنيد. براي به دست آوردن پشته هاي قابل تفسير در پيامهاي خطا از -fno-omit-frame-pointer استفاده كنيد. براي دقت كامل در پشته ها ممكن است نياز باشد درون خطي سازي را غيرفعال كنيد و از -fno-optimize-sibling-calls استفاده كنيد (براي نمونه، جلوگيري از آنكه يك فراخواني بازگشتي به شكل حلقه بازنويسي شود).
MemorySanitizer قادر است خاستگاه مقادير مقدارندهي نشده را ردگيري كند. اين قابليت با گزينه -fsanitize-memory-track-origins=2 (يا به اختصار -fsanitize-memory-track-origins) فعال ميشود.
نکته: كامپايلرهاي GNU از MemorySanitizer پشتيباني نميكنند.
در ادامه يك مثال با كامپايلرهاي AOCC ارائه شده است كه از كد آزموني درج شده در صفحه مستندات Clang استفاده ميكند:
متغير محيطي MSAN_OPTIONS ميتواند رفتار زمان اجراي فايل اجرايي ابزارگذاري شده را كنترل كند. براي مشاهده پرچمهاي رايج، به Sanitizer Flags مراجعه كنيد.
توجه داشته باشيد كه در محيطهاي PrgEnv-cray و PrgEnv-intel، مثال ياد شده اطلاعات خط كد مبدأ را نمايش نميدهد و اين مشكل به HPE گزارش شده است. اين مشكل را ميتوان با دستور زير برطرف كرد:
export MSAN_OPTIONS=”allow_addr2line=true”
راي اطلاعات بيشتر درباره اين ابزار به منابع زير مراجعه كنيد:
طبق مستند ياد شده، كندي معمول ناشي از MemorySanitizer حدود سه برابر است.
11. آشنایی با UndefinedBehaviorSanitizer (معروف به UBSan)
UndefinedBehaviorSanitizer (UBSan) يك شناسايي كننده سريع رفتار نامعين است. UBSan برنامه را در زمان كامپايل اصلاح ميكند تا انواع مختلفي از رفتار نامعين را هنگام اجراي برنامه كشف كند؛ براي نمونه:
- زيرنويسي آرايه خارج از مرز، در مواردي كه مرزها به صورت ايستا قابل تعيين باشند
- شيفت بيت كه خارج از دامنه مجاز براي نوع داده باشد
- ارجاع به اشاره گر ناصحيح يا تهی (null)
- سرريز عدد صحيح علامتدار
- تبديل به/از/ميان انواع عدد ممیز شناور كه سرريز نوع مقصد را به دنبال دارد
- فهرست كامل بررسيهاي[16] در دسترس در ادامه مستند رسمي آمده است.
UBSan يك كتابخانه زمان اجراي اختياري دارد كه گزارشهاي خطاي بهتري ارائه ميدهد. هزينه زمان اجراي اين بررسيها اندك است و هيچ تأثيري بر طرحبندي فضاي نشاني يا ABI ندارد.
11.1 روش ساخت
ساخت LLVM/Clang با [17]CMake انجام ميشود.
11.2 كاربرد
براي كامپايل و لينك برنامه، از clang++ همراه با گزينه -fsanitize=undefined استفاده كنيد. اطمينان حاصل كنيد كه براي گام نهايي لينك از clang++ استفاده ميكنيد (نه ld)، تا فايل اجرايي با كتابخانه هاي زمان اجراي صحيح UBSan لينك شود؛ مگر اينكه تمام بررسيهاي فعالشده در حالت trap باشند.
اگر كد شما C است ميتوانيد به جاي clang++ از clang استفاده كنيد.
cat test.cc
int main(int argc, char argv) {
int k = 0x7fffffff;
k += argc;
return 0;
}
clang++ -fsanitize=undefined test.cc
./a.out
test.cc:3:5: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
براي فعال يا غيرفعال كردن يك بررسي يا يك گروه از بررسيها ميتوانيد از -fsanitize=… و -fno-sanitize=… استفاده كنيد. براي يك بررسي منفرد، آخرين گزينهاي كه آن را فعال يا غيرفعال ميكند برنده است و اعمال ميشود.
Enable all checks in the "undefined" group, but disable "alignment".
clang -fsanitize=undefined -fno-sanitize=alignment a.c
Enable just "alignment".
clang -fsanitize=alignment a.c
The same. -fno-sanitize=undefined nullifies the previous -fsanitize=undefined.
clang -fsanitize=undefined -fno-sanitize=undefined -fsanitize=alignment a.c
براي بيشتر بررسيها، برنامه ابزارگذاري شده پس از وقوع خطا يك گزارش تفصيلي چاپ ميكند و به اجرا ادامه ميدهد. ميتوانيد با گزينههاي زير رفتار گزارشدهي خطا را تغيير دهيد:
- -fno-sanitize-recover=… : گزارش خطا را به صورت تفصيلي چاپ ميكند و برنامه را خاتمه ميدهد.
- -fsanitize-trap=… : يك دستور trap اجرا ميكند (نيازي به كتابخانه زمان اجراي UBSan ندارد). اگر اين سيگنال دريافت نشود، برنامه معمولاً با سيگنال SIGILL يا SIGTRAP خاتمه مييابد.
براي نمونه:
clang++ -fsanitize=signed-integer-overflow,null,alignment -fno-sanitize-recover=null -fsanitize-trap=alignment a.cc
برنامه پس از وقوع سرريز عدد صحيحِ علامتدار به اجرا ادامه خواهد داد، پس از نخستين استفاده نامعتبر از يك اشارهگر تهی (null) خاتمه مييابد، و پس از نخستين استفاده از يك اشارهگر ناصحيحِ همتراز نشده، trap ايجاد ميكند.
clang++ -fsanitize=undefined -fsanitize-trap=all a.cc
تمام بررسيهاي موجود در گروه undefined در حالت trap قرار ميگيرند. از آنجا كه هيچ يك از اين بررسيها به پشتيباني زمان اجرا نياز ندارد، كتابخانه زمان اجراي UBSan لينك نميشود. توجه داشته باشيد كه برخي sanitizerهاي ديگر نيز حالت trap را پشتيباني ميكنند و -fsanitize-trap=all حالت trap را براي همه آنها فعال ميكند.
clang -fsanitize-trap=undefined -fsanitize-recover=all a.c
-fsanitize-trap= و -fsanitize-recover= در غياب يك گزينه -fsanitize= بياثر هستند. در اين حالت هيچ هشدار گزينه بلااستفاده نيز نمايش داده نميشود.
11.3 بررسيهاي در دسترس
بررسيهاي زير در دسترس هستند:
- -fsanitize=alignment: استفاده از يك اشارهگر ناهماهنگ يا ايجاد يك مرجع ناهماهنگ. همچنين ويژگيهاي مشابه assume_aligned را نيز بررسي ميكند.
- -fsanitize=bool: بارگذاري يك مقدار bool كه نه true است و نه false.
- -fsanitize=builtin: ارسال مقادير نامعتبر به توابع دروني كامپايلر (compiler builtins).
- -fsanitize=bounds: زيرنويسي آرايه خارج از مرز در مواردي كه مرز آرايه به صورت ايستا قابل تشخيص باشد. اين بررسي شامل -fsanitize=array-bounds و -fsanitize=local-bounds است. توجه داشته باشيد كه -fsanitize=local-bounds در -fsanitize=undefined گنجانده نشده است.
- -fsanitize=enum: بارگذاري مقدار يك نوع شمارشي كه خارج از بازه قابل نمايش آن نوع باشد.
- -fsanitize=float-cast-overflow: تبديل به، از، يا ميان انواع ممیز شناور كه باعث سرريز نوع مقصد شود. از آنجا كه بازه قابل نمايش تمام انواع ممیز شناور پشتيبانيشده در Clang برابر [-inf, +inf] است، تنها تبديل از ممیز شناور به عدد صحيح بررسي ميشود.
- -fsanitize=float-divide-by-zero: تقسيم عدد ممیز شناور بر صفر. اين رفتار طبق استاندارد C/C++ نامعين است، اما در Clang (و استاندارد IEEE 754) مقدار inf يا NaN توليد ميشود، بنابراين اين مورد در -fsanitize=undefined قرار ندارد.
- -fsanitize=function: فراخواني غيرمستقيم يك تابع از طريق اشارهگر تابع با نوع ناسازگار.
- -fsanitize=implicit-unsigned-integer-truncation ، -fsanitize=implicit-signed-integer-truncation: تبديل ضمني از يك عدد با عرض بيت بزرگتر به عرض بيت كوچكتر وقتي كه اين كاهش باعث از دست رفتن داده شود؛ يعني اگر مقدار كوچكشده، بعد از بازگرداني به عرض بيت اوليه، برابر مقدار پيش از كاهش نباشد. نوع implicit-unsigned-integer-truncation براي تبديل بين انواع unsigned و نوع implicit-signed-integer-truncation براي ساير تبديلهاي شامل انواع signed استفاده ميشود. اين رفتارها نامعين نيستند اما معمولاً ناخواستهاند.
- -fsanitize=implicit-integer-sign-change: تبديل ضمني ميان انواع عدد صحيح وقتي كه تغيير علامت رخ دهد (مثلاً مقدار منفي به مثبت تبديل شود يا بالعكس). اين رفتار نامعين نيست اما غالباً ناخواسته است.
- -fsanitize=integer-divide-by-zero: تقسيم عدد صحيح بر صفر.
- -fsanitize=implicit-bitfield-conversion: تبديل ضمني از عدد با عرض بيت بزرگتر به يك bitfield كوچكتر كه منجر به از دست رفتن داده شود. اين مورد شامل كاهشهاي unsigned/signed و تغيير علامت است و مشابه گروه -fsanitize=implicit-integer-conversion عمل ميكند اما مخصوص bitfield ها است.
- -fsanitize=nonnull-attribute: ارسال اشارهگر تهی به پارامتري كه با _Nonnull علامتگذاري شده است.
- -fsanitize=null: استفاده از اشارهگر تهی يا ايجاد مرجع تهی.
- -fsanitize=nullability-arg: ارسال مقدار null به پارامتري كه با _Nonnull مشخص شده است.
- -fsanitize=nullability-assign: انتساب مقدار null به يك lvalue كه با _Nonnull مشخص شده است.
- -fsanitize=nullability-return: بازگرداني مقدار null از تابع با نوع بازگشتي مشخصشده با _Nonnull.
- -fsanitize=objc-cast: تبديل ضمني نادرست يك اشارهگر شيء Objective-C به نوع ناسازگار. اين رفتار اغلب ناخواسته است، اما نامعين نيست؛ به همين دليل در گروه undefined قرار ندارد. در حال حاضر فقط روي Darwin پشتيباني ميشود.
- -fsanitize=object-size: تلاش براي استفاده از بايتهايي كه بهطور قطعي خارج از اندازه شيء مورد دسترسي هستند (بر اساس __builtin_object_size). اين بررسي ميتواند موارد رفتاري را كشف كند كه لزوماً دسترسي مستقيم به حافظه ندارند اما با اندازه واقعي شيء ناسازگارند، مانند downcast نادرست يا فراخواني متد روي اشارهگر نامعتبر. در سطوح بالاتر بهينهسازي، مشكلات بيشتري قابل تشخيص هستند.
- -fsanitize=pointer-overflow: انجام محاسبات اشارهگري كه منجر به سرريز ميشود يا زماني كه مقدار اشارهگر جديد يا قديم تهی باشد (به جز حالتي كه هر دو تهی باشند).
- -fsanitize=return: در C++، رسيدن به انتهاي تابع داراي مقدار بازگشتي بدون return مناسب.
- -fsanitize=returns-nonnull-attribute: بازگرداندن اشارهگر تهی از تابع علامتگذاريشده با _Nonnull.
- -fsanitize=shift: عملگرهاي شيفت كه مقدار شيفت از عرض بيت ارتقايافته operand چپ بيشتر يا برابر باشد يا كمتر از صفر، يا operand چپ منفي باشد. در شيفت چپ علامتدار، سرريز علامتدار در C و سرريز unsigned در C++ نيز بررسي ميشود. ميتوانيد از -fsanitize=shift-base يا -fsanitize=shift-exponent براي بررسي فقط operand چپ يا راست استفاده كنيد.
- -fsanitize=unsigned-shift-base: بررسي شيفت چپ براي operand unsigned تا مطمئن شود سرريز رخ ندهد. اين رفتار نامعين نيست اما معمولاً ناخواسته است.
- -fsanitize=signed-integer-overflow: سرريز عدد صحيح علامتدار، هنگامي كه نتيجه محاسبه در نوع داده جا نميشود. شامل تمام موارد پوشش داده شده توسط -ftrapv و همچنين سرريز تقسيم (مانند INT_MIN / -1). توجه كنيد كه اين بررسي حتي اگر -fwrapv فعال باشد اضافه خواهد شد. اين بررسي تبديلهاي ضمني از دستدهنده داده قبل از محاسبه را بررسي نميكند (اين موارد در -fsanitize=implicit-integer-conversion پوشش داده ميشوند).
- -fsanitize=unreachable: اگر جريان كنترل به نقطه غيرقابل دسترس برنامه برسد.
- -fsanitize=unsigned-integer-overflow: سرريز عدد صحيح بدون علامت، وقتي كه نتيجه در نوع داده قابل بيان نيست. اين رفتار نامعين نيست اما غالباً ناخواسته است. اين بررسي نيز تبديلهاي ضمني از دستدهنده داده پيش از محاسبه را پوشش نميدهد.
- -fsanitize=vla-bound: آرايه با طول متغير كه مقدار مرز آن مثبت نباشد.
- -fsanitize=vptr: استفاده از شيء كه vptr آن نشان دهد از نوع پوياي نادرست است يا چرخه حيات آن شروع يا پايان نيافته است. اين بررسي با -fno-rtti ناسازگار است. لينك بايد با clang++ انجام شود نه clang، تا بخشهاي مخصوص C++ در زمان اجرا در دسترس باشند. اين بررسي بخشي از گروه undefined نيست و همچنين با -fsanitize-trap=vptr سازگار نيست.
شما همچنين ميتوانيد از گروههاي بررسي زير استفاده كنيد:
- -fsanitize=undefined: تمام بررسيهاي فهرستشده در بالا را فعال ميكند، به جز float-divide-by-zero، unsigned-integer-overflow، implicit-conversion، local-bounds، vptr و گروه nullability-*.
- -fsanitize=undefined-trap: نام منسوخشده براي -fsanitize=undefined.
- -fsanitize=implicit-integer-truncation: تبديلهاي عدد صحيح كه منجر به از دست رفتن داده شوند را شناسايي ميكند. اين گزينه implicit-signed-integer-truncation و implicit-unsigned-integer-truncation را فعال ميكند.
- -fsanitize=implicit-integer-arithmetic-value-change: تبديلهاي ضمنياي را كه باعث تغيير مقدار حسابي عدد شوند شناسايي ميكند. اين گزينه implicit-signed-integer-truncation و implicit-integer-sign-change را فعال ميكند.
- -fsanitize=implicit-integer-conversion: رفتار مشكوك در تبديلهاي ضمني عدد صحيح را بررسي ميكند و implicit-unsigned-integer-truncation، implicit-signed-integer-truncation و implicit-integer-sign-change را فعال ميكند.
- -fsanitize=implicit-conversion: رفتار مشكوك در تبديلهاي ضمني را بررسي ميكند و implicit-integer-conversion و implicit-bitfield-conversion را فعال ميكند.
- -fsanitize=integer: رفتارهاي نامعين يا مشكوك عددي (مانند unsigned integer overflow) را بررسي ميكند و signed-integer-overflow، unsigned-integer-overflow، shift، integer-divide-by-zero، implicit-unsigned-integer-truncation، implicit-signed-integer-truncation و implicit-integer-sign-change را فعال ميكند.
- -fsanitize=nullability: nullability-arg، nullability-assign و nullability-return را فعال ميكند. نقض nullability رفتار نامعين نيست، اما اغلب ناخواسته است و UBSan امكان شناسايي آن را فراهم ميكند.
11.4صفت (Volatile)
بررسیهای مربوط به null، alignment، object-size، local-bounds و vptr برای اشارهگرهایی که به انواع دارای صفت volatile اشاره میکنند اعمال نمیشود.
11.5 زمان اجراي حداقلي (Minimal Runtime)
يک زماناجرای حداقلی برای UBSan وجود دارد که برای استفاده در محیطهای تولیدی (production) مناسب است. اين زماناجرا سطح حمله کوچکی دارد، تنها امکان ثبت بسیار ساده مشکلات و حذف گزارشهای تکراری را فراهم میکند، و از بررسی -fsanitize=vptr پشتیبانی نمیکند.
برای استفاده از زماناجرای حداقلی، گزينه -fsanitize-minimal-runtime را به خط فرمان clang اضافه کنید.
برای مثال، اگر معمولاً با -fsanitize=undefined کامپايل میکنید، میتوانید زماناجرای حداقلی را اينگونه فعال کنید:
-fsanitize=undefined -fsanitize-minimal-runtime
11.6 رديابي پشته و نمادگذاري گزارش (Stack traces and report symbolization)
اگر میخواهید UBSan برای هر گزارش خطا یک stack trace نمادگذاریشده چاپ کند، لازم است:
- با -g ، -fno-sanitize-merge و -fno-omit-frame-pointer کامپايل کنید تا اطلاعات اشکالزدایی مناسب در دودویی ایجاد شود.
- برنامه را با متغیر محیطی زیر اجرا کنید:
UBSAN_OPTIONS=print_stacktrace=1
UBSAN_OPTIONS=log_path=...
Silencing Unsigned Integer Overflow
برای خاموشکردن گزارشهای مربوط به سرريز اعداد بدون علامت، میتوانید مقدار زیر را تنظیم کنید:
UBSAN_OPTIONS=silence_unsigned_overflow=1
این ویژگی، بهویژه در کنار -fsanitize-recover=unsigned-integer-overflow، برای فراهمکردن سیگنال لازم جهت fuzzing بدون تولید حجم زیاد لاگ بسیار مفید است.
11.8 غيرفعالسازي ابزارگذاري براي الگوهاي متداول سرريز
برخی الگوهای کدنویسی وابسته به سرريز یا مستعد سرريز بیش از حد گزارش تولید میکنند و باعث نویز زیاد برای sanitizers میشوند.
برای مثال:
ثابتهای unsigned منفیشده، پسکاهشها (post-decrement) در شرط حلقه while، و الگوهای ساده بررسی سرريز. این الگوها رایج و پذیرفتهشدهاند، اما گزارشهای حاصل از آنها ممکن است برای برخی پروژهها بیش از حد پرصدا باشند. برای غیرفعالکردن instrumentation در این الگوهای رایج، باید از گزینه زیر استفاده کرد:
-fsanitize-undefined-ignore-overflow-pattern=
در حال حاضر، این گزینه سه الگوی وابسته به سرريز را پشتیبانی میکند:
negated-unsigned-const
/// -fsanitize-undefined-ignore-overflow-pattern=negated-unsigned-const
unsigned long foo = -1UL; // No longer causes a negation overflow warning
unsigned long bar = -2UL; // and so on...
/// -fsanitize-undefined-ignore-overflow-pattern=unsigned-post-decr-while
unsigned char count = 16;
while (count--) { /* ... */ } // No longer causes unsigned-integer-overflow sanitizer to trip
/// -fsanitize-undefined-ignore-overflow-pattern=add-(signed|unsigned)-overflow-test
if (base + offset < base) { /* ... */ } // The pattern of `a + b < a`, and other re-orderings,
// won't be instrumented (signed or unsigned types)
انواع الگوهاي سرريز یا Overflow Pattern Types:
Sanitizer | Pattern |
unsigned-integer-overflow | negated-unsigned-const |
unsigned-integer-overflow | unsigned-post-decr-while |
unsigned-integer-overflow | add-unsigned-overflow-test |
signed-integer-overflow | add-signed-overflow-test |
اگر ادامه فهرست الگوها یا بخشهای بعدی متن را هم خواستی، بگو تا ترجمه کنم.
توجه: گزينه add-signed-overflow-test تنها بررسي رفتار تعريفنشده را غيرفعال ميكند. بهينهسازيهاي زودهنگام مبتني بر رفتار تعريفنشده همچنان ممكن هستند. براي رفع اين وضعيت ميتوان از -fwrapv يا -fno-strict-overflow استفاده كرد.
ميتوانيد همه استثناها را با -fsanitize-undefined-ignore-overflow-pattern=all فعال كنيد يا با -fsanitize-undefined-ignore-overflow-pattern=none همه را غيرفعال كنيد. اگر -fsanitize-undefined-ignore-overflow-pattern مشخص نشده باشد، مقدار none به طور ضمني در نظر گرفته ميشود. همچنين اگر none همراه با مقادير ديگر ذكر شود، باز هم none اعمال ميشود، زيرا نسبت به همه مقادير ديگر از جمله all اولويت دارد.
11.9 سركوب خطاها
ابزار UndefinedBehaviorSanitizer ذاتاً نبايد هشدار نادرست ايجاد كند. اگر موردي مشاهده كرديد، دوباره بررسي كنيد؛ در بيشتر موارد هشدار، واقعي است.
11.10 غيرفعالسازي ابزارگذاري با ویژگی ((no_sanitize(“undefined”)))
ميتوانيد با استفاده از __attribute__((no_sanitize(“undefined”))) بررسيهاي UBSan را براي توابع خاص غيرفعال كنيد. در اين خصيصه ميتوانيد تمام مقادير مربوط به فلگ -fsanitize= را نيز به كار ببريد. براي نمونه، اگر تابع شما عمداً شامل احتمال رخداد signed integer overflow است، ميتوانيد از __attribute__((no_sanitize(“signed-integer-overflow”))) استفاده كنيد.
اين خصيصه ممكن است در كامپايلرهاي ديگر پشتيباني نشود؛ از اين رو توصيه ميشود آن را همراه با #if defined(clang) به كار ببريد.
11.11 سركوب خطاها در كدِ بازكامپايل شده
ابزار UndefinedBehaviorSanitizer از انواع src و fun در Sanitizer special case list پشتيباني ميكند؛ اين موارد را ميتوان براي سركوب گزارش خطا در فايلهاي مبدا يا توابع مشخص شده به كار برد.
11.12 سركوبهاي زمان اجرا
گاهي ميتوان بدون نياز به بازكامپايل كردن كد، گزارشهاي خطاي UBSan را براي فايلها، توابع يا كتابخانه هاي مشخص سركوب كرد. براي اين كار بايد مسير فايل سركوب را در متغير محيطي UBSAN_OPTIONS قرار دهيد:
UBSAN_OPTIONS=suppressions=MyUBSan.supp
در اين فايل بايد نوع بررسي موردنظر براي سركوب و محل بروز خطا را مشخص كنيد. براي نمونه:
signed-integer-overflow:file-with-known-overflow.cpp
alignment:function_doing_unaligned_access
vptr:shared_object_with_vptr_failures.so
چند محدوديت وجود دارد:
- گاهي باينري شما بايد شامل اطلاعات كافيِ رفع اشكال و يا جدول نماد باشد تا زمان اجرا بتواند فايل مبدأ يا نام تابع را براي تطبيق با مورد سركوب تشخيص دهد.
- تنها ميتوان بررسيهاي قابل-بازيافت را سركوب كرد. براي نمونهاي كه در بالا آمده است، ميتوانيد -fsanitize-recover=signed-integer-overflow,alignment,vptr را نيز اضافه كنيد، هرچند بيشتر بررسيهاي UBSan به طور پيشفرض قابل-بازيافت هستند.
- گروههاي بررسي (مانند undefined) را نميتوان در فايل سركوب به كار برد؛ تنها بررسيهاي دقيق و جزئي پشتيباني ميشوند.
11.13 ملاحظات امنيتي
زمان اجراي UndefinedBehaviorSanitizer براي مقاصد آزمون طراحي شده است و استفاده از آن در محيط توليدي بايد از منظر امنيتي با دقت بررسي شود، زيرا ممكن است امنيت اجرايي نهايي را تضعيف كند. براي كاربردهاي حساس به امنيت، استفاده از [18]Minimal Runtime يا حالت trap mode براي تمام بررسيها توصيه ميشود.
11.14 پلتفرمهاي پشتيباني شده
UndefinedBehaviorSanitizer بر روي سامانههاي عامل زير پشتيباني ميشود:
- Android
- Linux
- NetBSD
- FreeBSD
- OpenBSD
- macOS
- Windows
كتابخانه زمان اجرا نسبيًا قابل انتقال و مستقل از پلتفرم است. اگر سيستمعاملي كه نياز داريد در فهرست بالا نيست، ممكن است UndefinedBehaviorSanitizer از قبل روي آن كار كند يا با اندكي تلاش براي انتقال، قابل اجرا باشد.
11.15 وضعيت كنوني
UndefinedBehaviorSanitizer از LLVM نسخه ۳.۳ به بعد بر روي برخي پلتفرمها در دسترس است. مجموعه آزمون آن در سيستم ساخت CMake يكپارچه شده و با دستور check-ubsan قابل اجرا است.
11.16 پيكربندي اضافي
UndefinedBehaviorSanitizer براي هر بررسي، دادههاي ايستا اضافه ميكند مگر اينكه در حالت trap باشد. اين داده شامل نام كامل فايل است. گزينه -fsanitize-undefined-strip-path-components=N براي كاهش اجزاي مسير قابل استفاده است.
اگر N مثبت باشد، اطلاعات فايلِ خروجي، N جزء ابتدايي مسير را حذف ميكند.
اگر N منفي باشد، تنها N جزء پاياني مسير حفظ ميشود.
11.17 مثال
براي فايلي با نام /code/library/file.cpp موارد زير توليد ميشود:
• Default (No flag, or -fsanitize-undefined-strip-path-components=0): /code/library/file.cpp
• -fsanitize-undefined-strip-path-components=1: code/library/file.cpp
• -fsanitize-undefined-strip-path-components=2: library/file.cpp
• -fsanitize-undefined-strip-path-components=-1: file.cpp
• -fsanitize-undefined-strip-path-components=-2: library/file.cpp
11.18 اطلاعات بيشتر
From Oracle blog, including a discussion of error messages: Improving Application Security with UndefinedBehaviorSanitizer (UBSan) and GCC[19]
From LLVM project blog: What Every C Programmer Should Know About Undefined Behavior[20]
From John Regehr’s Embedded in Academia blog: A Guide to Undefined Behavior in C and C++[21]
پاورقی:
[1] https://github.com/google/sanitizers/wiki/AddressSanitizer
[2] https://github.com/google/sanitizers/wiki/ThreadSanitizerFlags
[3] https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
[4] experimental
[5] https://clang.llvm.org/docs/LeakSanitizer.html
[6] https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions
[۷] https://github.com/google/sanitizers/wiki/ThreadSanitizerFlags
[۸] https://clang.llvm.org/docs/ThreadSanitizer.html
[۹] https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual
[10] https://llvm.org/docs/CMake.html
[۱۱] https://clang.llvm.org/docs/MemorySanitizer.html#msan-origins
[۱۲] https://clang.llvm.org/docs/LanguageExtensions.html#langext-has-feature-has-extension
[13] https://eel.is/c++draft/basic.life#1
[14] https://clang.llvm.org/docs/MemorySanitizer.html
[15] https://github.com/google/sanitizers/wiki/MemorySanitizer
[16] https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#ubsan-checks
[17] https://llvm.org/docs/CMake.html
[18] https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#minimal-runtime
[19] https://blogs.oracle.com/linux/improving-application-security-with-undefinedbehaviorsanitizer-ubsan-and-gcc
[20] http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
[21] https://blog.regehr.org/archives/213