1. مقدمه
واژه «سنیتایزر» (Sanitizer) در زبان انگلیسی به معنای ضدعفونیکننده یا پاککننده است، اما در حوزه علوم کامپیوتر—بهویژه در اکوسیستمهای LLVM و Clang—معنایی کاملاً تخصصی و متفاوت دارد. در این زمینه، سنیتایزر به ابزاری گفته میشود که برای تشخیص خطاهای زمان اجرا از طریق ابزارگذاری (Instrumentation) به کار میرود. به بیان دقیقتر، Code Sanitizer ابزاری است که با تزریق کدهای نظارتی در مرحله کامپایل، رفتار برنامه را در زمان اجرا پایش کرده و خطاهای مربوط به رفتارهای تعریفنشده یا مشکوک را شناسایی میکند.
این دسته از ابزارها نخستینبار با معرفی AddressSanitizer (ASan) توسط گوگل در سال ۲۰۱۲ شناخته شدند؛ ابزاری که با استفاده از مفهوم Shadow Memory و نگاشت مستقیم حافظه، قادر است انواع خطاهای حافظه مانند سرریز بافر (Buffer Overflow) یا دسترسی به اشارهگر نامعتبر (Dangling Pointer) را شناسایی کند. از این رو، در این سند نیز از واژه «سنیتایزر» برای اشاره به این خانواده از ابزارها استفاده میشود.
Sanitizerهای LLVM مجموعهای از ابزارهای خطایابی هستند که برای شناسایی انواع مختلف باگها در کدهای C و ++C مورد استفاده قرار میگیرند. این مجموعه شامل ابزارهایی مانند AddressSanitizer (ASan)، LeakSanitizer (LSan)، ThreadSanitizer (TSan) و MemorySanitizer (MSan) است که هر یک قابلیتهای تخصصی برای شناسایی دستهای خاص از خطاها ارائه میدهند.
یک سنیتایزر معمولاً از دو بخش اصلی تشکیل میشود:
۱. ماژول ابزاردگذاری (Instrumentation) در زمان کامپایل
۲. کتابخانه زمان اجرا (Runtime Library)
برای استفاده از یک سنیتایزر، ابتدا باید نسخه ابزارگذاریشده برنامه با فعالسازی فلگ مربوطه در زمان کامپایل ساخته شود. سپس هنگام اجرای برنامه، کتابخانه زمان اجرا رفتار برنامه را پایش کرده و در صورت مشاهده خطا، گزارش هشدار یا خطای مناسب تولید میکند.
به دلیل وجود ابزارگذاری و سربارهای مربوط به آن، ممکن است مصرف حافظه بهطور قابل توجهی افزایش یابد و سرعت اجرای برنامه کاهش پیدا کند. بنابراین، پس از اتمام مرحله اشکالزدایی، بازسازی نسخه نهایی برنامه بدون ابزارگذاری از اهمیت بالایی برخوردار است.
2. ابزارگذاری چیست؟ (What Is Instrumentation)
ابزارگذاری (Instrumentation) به فرایند افزودن کد به یک برنامه گفته میشود تا امکان مشاهده و تحلیل رفتار درونی آن فراهم شود. برنامههای ابزارگذاریشده، رفتار کد را هنگام پاسخگویی به درخواستهای اجرایی اندازهگیری کرده و دادههایی مانند شاخصها (Metrics)، رویدادها (Events)، گزارشها (Logs) و ردیابیها (Traces) را—که بهاختصار با عنوان MELT شناخته میشوند—جمعآوری میکنند.
در نتیجه، زمانی که یک برنامه بهطور کامل ابزاردهی میشود، نحوه جمعآوری و گزارشگیری از دادههای پایش (Monitoring) مشخص شده و همچنین شیوه مشاهده سایر اطلاعاتی که میتوانند دید عمیقتری نسبت به رفتار سیستم ارائه دهند، تعریف میشود.
در مقابلِ یک برنامه غیر ابزاردهیشده که صرفاً به گزارشهای لحظهای (Ad-hoc Logs) متکی است، یک برنامه ابزارگذاری شده تا حد امکان اطلاعات مربوط به عملیات و رفتار سرویس را ثبت و ردیابی میکند. این رویکرد باعث میشود جزئیات بیشتری از رخدادها در دسترس قرار گیرد و امکان تحلیل ارتباط بین درخواستها و مسیر اجرای آنها فراهم شود. از جمله ابزارها و پلتفرمهای شناخته شده در زمینه ابزارگذاری میتوان به Valgrind، DynamoRIO و Intel PIN اشاره کرد.
3. ابزارگذاری دقیقاً چهکار میکند؟ (What exactly does instrumentation do?)
وقتی یک برنامه را ابزارگذاری میکنیم، یعنی:
- در نقاط مختلف برنامه کد اضافی (instrumentation code) تزریق یا فعال میشود.
- این کد اضافی اطلاعات زمان اجرا را جمع آوری میکند، مانند:
- خطاهای حافظه
- رویدادها
- مقادیر متغیرها
- مسیرهای اجرای برنامه
- گزارشها (logs)
- ردیابیها (traces)
- شاخصها (metrics)
به همین دلیل است که سنیتایزرها (Sanitizer) یا Profilerها فقط با ابزارگذاری کارمیکنند.
4. پروفایلر دقیقاً چهکار میکند؟ (What exactly does a profiler do?)
پروفایلر (Profiler) هنگام اجرای برنامه با اندازهگیری زمان اجرای هر تابع، شمارش تعداد فراخوانیها، ثبت مصرف CPU، سنجش مصرف حافظه، تحلیل مسیرهای اجرا و شناسایی بخشهای کند کد، اطلاعات دقیقی برای بهینهسازی سرعت نرمافزار در اختیار برنامهنویس قرار میدهد.
5. تفاوت پروفایلر و سنیتایزر ( Difference between profiler and sanitizer)
ابزار | کار اصلی | کاربرد |
Sanitizer | پیدا کردن باگهای خطرناک زمان اجرا | تشخیص خطاهای حافظه، Race، Undefined Behavior |
Profiler | اندازهگیری عملکرد و سرعت برنامه | بهینهسازی سرعت، پیدا کردن نقاط کُند |
6. کامپایلرهای پشتیبانی شده (Supported compilers)
این ابزارها تنها محدود به کامپایلرهای 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 Flag)
این کامپایلرها مجموعه گستردهای از فلگهای کامپایل مرتبط با Sanitizerهای LLVM را پشتیبانی میکنند و میتوان از آنها بسته به نیاز پروژه استفاده کرد. بهعنوان مثال، نیازی نیست کل کد بهصورت کامل ابزارگذاری شود؛ بلکه میتوان با استفاده از گزینههای -fsanitize-blacklist یا -fsanitize-ignorelist برخی توابع یا فایلهای مبدأ را از فرآیند ابزارگذاری مستثنی کرد.
رفتار زمان اجرای هر ابزار از طریق متغیرهای محیطی مربوط به Sanitizer و تنظیم آنها با فلگهای زمان اجرا کنترل میشود. این متغیرها شامل موارد زیر هستند:
- 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 نحوه ساخت (How to make)
ساخت 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 كاربرد (Use case)
برای استفاده از AddressSanitizer کافی است برنامه خود را با گزینه -fsanitize=address کامپایل و لینک کنید. توجه داشته باشید که کتابخانه زمان اجرای AddressSanitizer باید در فایل اجرایی نهایی لینک شود؛ بنابراین باید در مرحله نهایی لینک، از clang استفاده کنید، نه از ld.
هنگام لینک کردن کتابخانههای اشتراکی، ممکن است کتابخانه زمان اجرای AddressSanitizer بهدرستی لینک نشود؛ در نتیجه استفاده از گزینه -Wl,-z,defs میتواند باعث خطای لینک شود. بنابراین توصیه میشود این گزینه را همراه با AddressSanitizer به کار نبرید.
برای دستیابی به کارایی قابل قبول، استفاده از سطح بهینهسازی -O1 یا بالاتر توصیه میشود. همچنین برای دریافت stack traceهای خواناتر در پیامهای خطا، میتوانید از گزینه -fno-omit-frame-pointer استفاده کنید.
برای بهدست آوردن stack traceهای دقیقتر، ممکن است لازم باشد درونخطیسازی (Inlining) را محدود کنید (مثلاً فقط از -O1 استفاده کنید) و همچنین بهینهسازی فراخوانیهای انتهایی (Tail Calls) را با گزینه -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 نمادگذاری گزارشها (Logs Symbolize)
برای اینکه AddressSanitizer بتواند خروجی خود را نمادگذاری (Symbolize) کند، باید متغیر محیطی 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 اجرا میشود)، میتوانید از یک اسکریپت جداگانه برای انجام نمادگذاری (Symbolization) بهصورت آفلاین استفاده کنید. در این حالت، نمادگذاری در زمان اجرا انجام نمیشود و خروجی خام بعداً پردازش خواهد شد. همچنین میتوان نمادگذاری آنلاین را بهطور کامل با تنظیم متغیر محیطی 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 را اجرا کنید تا اطلاعات file در گزارشهای AddressSanitizer در دسترس قرار گیرد.
8.4 بررسيهاي افزوده (Additional reviews)
8.4.1 بررسي ترتيب آغازينسازي (Checking the initialization sequence)
AddressSanitizer ميتواند به شکل اختياري مشکلات ترتيب آغازين سازي پويا را شناسايي کند؛ موقعي که آغازين سازي سراسريهاي تعريف شده در يک واحد ترجمه از سراسريهاي تعريف شده در واحد ترجمه ديگر استفاده ميکند. براي فعال کردن اين بررسي در زمان اجرا بايستی متغير محيطي ASAN_OPTIONS=check_initialization_order=1 را تنظيم کنيد. توجه داشته باشيد که اين گزينه روي macOS پشتيباني نميشود.
8.4.2 استفاده از پشته پس از بازگشت (UAR – Use-After-Return)
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 شناسايي نشت حافظه (Memory leak detection)
براي اطلاعات بيشتر درباره شناسايي كننده نشت در AddressSanitizer به LeakSanitizer مراجعه كنيد. شناسايي نشت به طور پيش فرض روي Linux فعال است و روي macOS ميتوان آن را با ASAN_OPTIONS=detect_leaks=1 فعال كرد. اين قابليت هنوز روي ساير پلتفرمها پشتيباني نميشود.
8.5 مهار خطا (Error Suppression)
انتظار نمیرود AddressSanitizer هشدار نادرست (False Positive) تولید کند. اگر چنین موردی مشاهده کردید، آن را دوباره بررسی کنید؛ در اغلب موارد، احتمال بسیار زیادی وجود دارد که هشدار گزارششده واقعی و صحیح باشد.
8.5.1 مهار گزارشها در كتابخانههاي خارجي (Suppression of reports in external libraries)
جایگزینی زمان اجرا این امکان را برای AddressSanitizer فراهم میکند که خطاها را حتی در کدی که مجدداً کامپایل نشده است نیز شناسایی کند. اگر در کتابخانههای خارجی با مشکلی مواجه شدید، توصیه میشود آن را در اولین فرصت به نگهدارنده همان کتابخانه گزارش کنید تا اصلاح شود. با این حال، برای رفع انسداد و ادامه فرایند آزمون، میتوانید از مکانیزم مهار (Suppression) زیر استفاده کنید. این سازوکار تنها باید برای مهار مشکلات موجود در کدهای خارجی به کار رود و روی کدی که با AddressSanitizer مجدداً کامپایل شده باشد اعمال نمیشود. برای مهار خطاها در کتابخانههای خارجی، میتوانید متغیر محیطی ASAN_OPTIONS را تنظیم کنید تا به یک فایل مهار (suppression file) اشاره کند. میتوان مسیر کامل فایل یا مسیر نسبی آن نسبت به محل فایل اجرایی را مشخص کرد:
ASAN_OPTIONS=suppressions=MyASan.supp
از قالب زیر برای مشخص کردن نام توابع یا کتابخانههایی که میخواهید مهار (Suppress) شوند استفاده کنید. این نامها را میتوانید در گزارشهای خطا مشاهده کنید. توجه داشته باشید که هرچه دامنه مهار محدودتر باشد، تعداد بیشتری از باگها قابل شناسایی خواهند بود.
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 دیگر ناحیههای حفاظتی (Redzones) را پیرامون آن اضافه نکند و در نتیجه دسترسی خارج از محدوده مربوط به آن را نیز تشخیص ندهد.
AddressSanitizer همچنین از attribute((disable_sanitizer_instrumentation)) پشتیبانی میکند. عملکرد این ویژگی مشابه attribute((no_sanitize("address"))) است، با این تفاوت که علاوه بر آن، ابزارگذاری انجامشده توسط سایر سنیتایزرها (Sanitizer) را نیز غیرفعال میکند.
8.5.4 مهار خطا در كد دوباره كامپايل شده (Error handling in recompiled code)
AddressSanitizer از موجودیتهای src و fun در فهرست موارد خاص Sanitizer پشتیبانی میکند. این موارد میتوانند برای مهار گزارشهای خطا در فایلهای مبدأ یا توابع مشخص استفاده شوند. علاوه بر این، AddressSanitizer موجودیتهای global و type را نیز معرفی میکند که برای مهار گزارشهای مربوط به دسترسی خارج از محدوده (Out-of-bounds) روی متغیرهای سراسری با نامها و نوعهای مشخص به کار میروند (تنها نوعهای class یا struct مجاز هستند). همچنین میتوان از دسته init برای مهار گزارشهای مربوط به مشکلات ترتیب مقداردهی اولیه (Initialization Order Issues) استفاده کرد؛ این مشکلات ممکن است در فایلهای مبدأ یا متغیرهای سراسری خاص رخ دهند.
# 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
LSAN_OPTIONS=suppressions=MyLSan.supp
این فایل باید شامل خطوطی با قالب leak:<pattern> باشد. نشت حافظه زمانی مهار میشود که pattern با هر یک از نام تابع، نام فایل مبدأ یا نام کتابخانه موجود در پشته نمادگذاریشده گزارش نشت حافظه مطابقت داشته باشد. برای جزئیات بیشتر به مستند کامل مراجعه کنید [6].
8.6 كنترل توليد كد (Code generation control)
8.6.1 تفكيك ابزارگذاري از كد (Separating tooling from code)
AddressSanitizer بهصورت پیشفرض برای بهبود کارایی در زمان اجرا، کد ابزارگذاری را درونخطی (Inline) میکند که این موضوع میتواند باعث افزایش اندازه فایل دودویی شود. استفاده از گزینه -fsanitize-address-outline-instrumentation (با مقدار پیشفرض: false) در Clang باعث میشود تمام ابزارگذاریها بهصورت جداشده (Out-of-line) انجام شوند. این کار اندازه کد تولیدی را کاهش میدهد، اما در مقابل کارایی زمان اجرا را کاهش خواهد داد.
8.7 محدوديتها (Limitations)
AddressSanitizer نسبت به اجرای معمول، حافظه واقعی بیشتری مصرف میکند. میزان این سربار به اندازه تخصیصها بستگی دارد؛ هرچه تخصیصهای شما کوچکتر باشند، میزان سربار بیشتر خواهد بود. AddressSanitizer همچنین حافظه پشته (Stack) بیشتری مصرف میکند و در برخی موارد افزایش مصرف تا سه برابر مشاهده شده است.
در سیستمهای ۶۴ بیتی، AddressSanitizer فضای نشانی مجازی (Virtual Address Space) به اندازه ۱۶ ترابایت یا بیشتر را نگاشت میکند (اما آن را بهطور کامل رزرو نمیکند). این موضوع باعث میشود ابزارهایی مانند ulimit رفتار متفاوتی نسبت به حالت عادی داشته باشند. همچنین لینک ایستا (Static Linking) برای فایل اجرایی پشتیبانی نمیشود.
8.8 ملاحظات امنيتي (Security considerations)
AddressSanitizer یک ابزار کشف باگ است و زمان اجرای آن برای لینک شدن با برنامههای تولیدی در محیطهای واقعی طراحی نشده است. اگرچه استفاده از آن برای آزمون و اشکالزدایی بسیار مفید است، اما کتابخانه زمان اجرای آن با ملاحظات امنیتی سختگیرانه توسعه داده نشده و ممکن است در برخی موارد باعث کاهش سطح امنیت فایل اجرایی نهایی شود.
8.9 پلتفرمهاي پشتيباني شده (Supported platforms)
AddressSanitizer روي پلتفرمهای زير پشتيباني ميشود:
- Linux
- macOS
- iOS Simulator
- Android
- NetBSD
- FreeBSD
- Windows 8.1+
8.10 وضعيت كنوني (Current status)
AddressSanitizer از LLVM 3.1 به بعد روي پلتفرمهای پشتيباني شده كاملاً كاربردي است. مجموعه آزمون آن در فرآيند ساخت CMake يكپارچه شده و با دستور make check-asan قابل اجرا است. نسخه Windows كاربردي است و توسط Chrome و Firefox استفاده ميشود، اما به اندازه نسخههاي ديگر پشتيباني شده نيست.
8.11 اطلاعات بيشتر (More information)
https://github.com/google/sanitizers/wiki/AddressSanitizer
8.12 مروری بر AddressSanitizer با روایتی دیگر (A review of AddressSanitizer with another story)
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 Race) است. این ابزار از یک ماژول ابزارگذاری در کامپایلر و یک کتابخانه زمان اجرا تشکیل میشود. میزان کندی اجرای برنامه در حالت استفاده از ThreadSanitizer معمولاً حدود ۵ تا ۱۵ برابر است. همچنین سربار حافظه آن نیز بهطور معمول حدود ۵ تا ۱۰ برابر افزایش مییابد.
9.1 روش ساخت (Manufacturing method)
ساخت LLVM/Clang با CMake انجام ميشود.
9.2 پلتفرمهاي پشتيباني شده
ThreadSanitizer روي سیستمعاملهاي زير پشتيباني ميشود:
- Android: aarch64، x86_64
- Darwin: arm64، x86_64
- FreeBSD
- Linux: aarch64، x86_64، powerpc64، powerpc64le
- NetBSD
پشتيباني براي ساير معماري هاي ٦٤ بيتي ممكن است و از مشاركت استقبال ميشود. پشتيباني براي پلتفرمهاي ٣٢ بيتي دشوار است و در برنامه قرار ندارد.
9.3 كاربرد (Use Case)
كافي است برنامه خود را با گزينه -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 برای نمادگذاری (symbolization) خروجی خود از یک فرایند خارجی به نام 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 همچنان این تابع را ابزارگذاری میکند تا از هشدارهای نادرست جلوگیری کرده و پشتههای (stack traces) قابلاعتماد ارائه دهد. ممکن است این ویژگی توسط سایر کامپایلرها پشتیبانی نشود، بنابراین توصیه میشود همراه با has_feature(thread_sanitizer)__ استفاده شود.
9.6 __attribute__((disable_sanitizer_instrumentation))
این ویژگی میتواند روی توابع اعمال شود تا تمام انواع ابزارگذاری را غیرفعال کند. استفاده از آن ممکن است منجر به هشدارهای نادرست و پشتههای (stack traces) نادقیق شود. بنابراین تنها باید در موارد کاملاً ضروری به کار رود؛ برای مثال در کدی که هیچگونه ابزارگذاری یا اثر جانبی ناشی از آن را نمیتواند تحمل کند. این ویژگی نسبت به no_sanitize("thread") ارجحیت دارد.
9.7 لیست نادیده گرفته شده (Ignorelist)
ThreadSanitizer از موجودیتهای src و fun در فهرست موارد خاص Sanitizer پشتیبانی میکند. این موارد میتوانند برای مهار گزارشهای رقابت دادهای (data race) در فایلهای مبدأ یا توابع مشخص به کار روند. برخلاف توابعی که با no_sanitize("thread") علامتگذاری شدهاند، توابع موجود در ignorelist بهطور کامل ابزارگذاری نمیشوند. این موضوع ممکن است به هشدارهای نادرست منجر شود، که علت آن از دست رفتن همزمانی (concurrency) از طریق عملیات اتمی و همچنین حذف فریمهای پشته در گزارشها است.
9.8 محدوديتها (Limitations)
ThreadSanitizer نسبت به اجرای بومی، حافظه واقعی بیشتری مصرف میکند. در تنظیمات پیشفرض، سربار حافظه حدود پنج برابر بهعلاوه یک مگابایت برای هر رشته (thread) است. همچنین تنظیماتی با سربار سه برابر (برای تحلیل کمدقتتر) و نه برابر (برای تحلیل دقیقتر) نیز در دسترس هستند. ThreadSanitizer بخش بزرگی از فضای نشانی مجازی را نگاشت میکند (اما آن را بهطور کامل رزرو نمیکند). این موضوع باعث میشود ابزارهایی مانند ulimit ممکن است مطابق انتظار معمول عمل نکنند.
لینک ایستا برای libc و ++libstdc پشتیبانی نمیشود. همچنین فایلهای اجرایی غیرقابل جابجایی (non-position-independent) نیز پشتیبانی نمیشوند. بنابراین استفاده از -fsanitize=thread باعث میشود Clang در صورت عدم استفاده از -fPIC بهگونهای عمل کند که گویی -fPIE فعال شده است، و در مرحله لینک نیز مانند حالتی رفتار میکند که -pie تنظیم شده باشد.
9.9 ملاحظات امنيتي (Security considerations)
ThreadSanitizer یک ابزار کشف باگ است و زمان اجرای آن برای لینک شدن با فایلهای اجرایی تولیدی در محیطهای واقعی طراحی نشده است. اگرچه استفاده از آن برای آزمون بسیار مفید است، اما کتابخانه زمان اجرای آن با محدودیتها و ملاحظات امنیتی سختگیرانه توسعه داده نشده و ممکن است در برخی موارد باعث کاهش سطح امنیت فایل اجرایی نهایی شود.
9.10 وضعيت كنوني (Current status)
ThreadSanitizer در مرحله بتا قرار دارد. این ابزار روی برنامههای بزرگ ++C که از pthreads استفاده میکنند کار میکند، اما هنوز هیچ تضمینی برای عملکرد کامل آن ارائه نمیشود. پشتیبانی از نخسازی (threading) در ++C11 از طریق ++libc نیز فراهم شده است. مجموعه آزمونهای آن در فرآیند ساخت CMake یکپارچه شده و میتوان آن را با دستور make check-tsan اجرا کرد. ما بهصورت فعال در حال بهبود این ابزار هستیم — همراه ما باشید. هرگونه کمک، بهویژه در قالب آزمونهای مستقل و کوچک، بسیار ارزشمند خواهد بود.
9.11 اطلاعات بيشتر (More information)
https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual
9.12 مروری بر ThreadSanitizer با روایتی دیگر (A review of ThreadSanitizer with another story)
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 مراجعه کنید.
ممکن است لازم باشد یک فایل اجراییِ ابزارگذاریشده را چندین بار اجرا کنید؛ زیرا اگر یک شرایط رقابتی (race condition) در یک اجرا رخ ندهد، حتی در صورت وجود باگ در کد، هیچ پیام هشداری دریافت نخواهید کرد. برای اطلاعات بیشتر درباره این ابزار، به منابع زیر مراجعه کنید:
طبق یکی از صفحات ذکرشده، هزینه شناسایی رقابت دادهای بسته به نوع برنامه متفاوت است؛ با این حال در یک برنامه معمول، مصرف حافظه ممکن است حدود ۵ تا ۱۰ برابر افزایش یابد و زمان اجرا نیز بین ۲ تا ۲۰ برابر کندتر شود.
10. آشنایی با MemorySanitizer (معروف به MSan)
MemorySanitizer یک ابزار شناسایی استفاده از حافظه مقداردهینشده (Uninitialized Memory Use) است. این ابزار از یک ماژول ابزارگذاری در کامپایلر و یک کتابخانه زمان اجرا تشکیل میشود. میزان کندی معمول ایجادشده توسط MemorySanitizer حدود ۳ برابر است. در فهرست زیر برخی از مواردی که MemorySanitizer در آنها خطا گزارش میکند آمده است (این فهرست کامل نیست):
- استفاده از مقدار مقدارندهي نشده در يك شاخه شرطي
- استفاده از اشاره گر مقدارندهي نشده براي دسترسي به حافظه
- ارسال يا بازگرداندن مقدار مقدارندهي نشده از يك فراخواني تابع، كه رفتار نامعين محسوب ميشود. اين بررسي را ميتوان با -fno-sanitize-memory-param-retval غيرفعال كرد.
- ارسال داده مقدارندهي نشده به برخي فراخواني هاي libc
10.1 روش ساخت (Manufacturing method)
ساخت LLVM/Clang با CMake[۱۰] انجام ميشود.
10.2 كاربرد (Use Case)
کافی است برنامه خود را با گزینه -fsanitize=memory کامپایل و لینک کنید. کتابخانه زمان اجرای MemorySanitizer باید به فایل اجرایی نهایی لینک شود، بنابراین در مرحله نهایی لینک حتماً از clang استفاده کنید، نه ld. هنگام لینک کتابخانههای اشتراکی، کتابخانه زمان اجرای MemorySanitizer بهطور خودکار لینک نمیشود؛ بنابراین استفاده از -Wl,-z,defs ممکن است باعث خطای لینک شود (این گزینه را همراه MemorySanitizer استفاده نکنید).
برای کارایی مناسب، گزینه -O1 یا بالاتر را اضافه کنید. برای دریافت stack traceهای معنادار در پیامهای خطا، از -fno-omit-frame-pointer استفاده کنید. همچنین برای دقت بالاتر در گزارشها ممکن است لازم باشد inlining را غیرفعال کنید (فقط از -O1 استفاده شود) و حذف فراخوانیهای دنبالهای (tail calls) نیز غیرفعال شود با -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 نمادگذاري گزارش (Symbolizing Reports)
MemorySanitizer برای نمایش نام فایل و شماره خط در گزارشها از یک نمادگذار خارجی (external symbolizer) استفاده میکند. اطمینان حاصل کنید که برنامه 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 قابلیت شناسایی استفاده پس از نابودی (use-after-destruction) را نیز شامل میشود. پس از فراخوانی تابع مخرب (destructor)، شیء دیگر قابل خواندن در نظر گرفته نمیشود و هرگونه استفاده از حافظه زیربنایی آن در زمان اجرا منجر به گزارش خطا خواهد شد. برای تعریف چرخه حیات (lifetime)، به استاندارد مراجعه کنید [13]. این قابلیت را میتوان با یکی از روشهای زیر غیرفعال کرد:
- افزودن گزينه -fno-sanitize-memory-use-after-dtor هنگام كامپايل
- تنظيم متغير محيطي MSAN_OPTIONS=poison_in_dtor=0 پيش از اجراي برنامه
10.6 پردازش كد خارجي (External code processing)
MemorySanitizer مستلزم آن است که تمام کد برنامه ابزارگذاری شود؛ این شامل تمامی کتابخانههایی که برنامه به آنها وابسته است (حتی libc) نیز میشود. عدم تحقق این شرط میتواند منجر به هشدارهای نادرست شود. به همین دلیل، ممکن است لازم باشد تمام کدهای اسمبلی درونخطی (inline assembly) که در حافظه مینویسند با معادلهای خالص C یا ++C جایگزین شوند. دستیابی به ابزارگذاری کامل برای MemorySanitizer بسیار دشوار است. برای سادهتر کردن این فرایند، کتابخانه زمان اجرای MemorySanitizer بیش از ۷۰ نگهدارنده (interceptor) برای رایجترین توابع libc ارائه میدهد. این نگهدارندهها امکان اجرای برنامههای ابزارگذاریشده را در کنار libc ابزارگذارینشده فراهم میکنند. برای نمونه، توسعهدهندگان توانستهاند کامپایلر Clang ابزارگذاریشده با MemorySanitizer را با لینک کردن آن به ++libc ابزارگذاریشده (به جای ++libstdc) بسازند.
10.7 ملاحظات امنيتي (Security considerations)
MemorySanitizer يك ابزار كشف باگ است و زمان اجراي آن براي لينك شدن با فايلهاي اجرايي توليدي در محيط واقعي طراحي نشده است. هرچند براي آزمون سودمند است، اما زمان اجراي آن با قيود حساس به امنيت توسعه داده نشده و ممكن است امنيت فايل اجرايي را كاهش دهد.
10.8 پلتفرمهاي پشتيباني شده (Supported platforms)
MemorySanitizer روي سیستمعاملهای زير پشتيباني ميشود:
- Linux
- NetBSD
- FreeBSD
10.9 محدوديتها (Limitations)
- MemorySanitizer نسبت به اجراي بومي دو برابر حافظه واقعي مصرف ميكند؛ با رديابي خاستگاه، سه برابر.
- MemorySanitizer فضاي نشاني مجازي ٦٤ ترابايت را نگاشت ميكند (اما رزرو نميكند). اين بدان معناست كه ابزارهايي مانند ulimit ممكن است مطابق انتظار معمول عمل نكنند.
- لينك ايستا پشتيباني نميشود.
- نسخه هاي قديمي تر (LLVM 3.7 و پيش از آن) با اجرايي هاي غيرقابل جابجايي سازگار نبودند و ممكن بود روي نسخه هايي از هسته Linux با ASLR غيرفعال دچار خطا شوند. براي جزئيات به مستند نسخه هاي قديمي مراجعه كنيد.
- MemorySanitizer ممكن است با اجرايي هاي قابل جابجايي (PIE) در FreeBSD 13 سازگار نباشد، اما يك بررسي زمان اجرا وجود دارد كه در اين حالت هشدار ميدهد.
10.10 وضعيت كنوني (Current status)
MemorySanitizer روی برنامههای بزرگ و واقعی (از جمله خود Clang/LLVM) که امکان کامپایل مجدد از منبع را دارند، بهخوبی کار میکند؛ از جمله زمانی که تمام کتابخانههای وابسته نیز دوباره ابزارگذاری و کامپایل شده باشند.
10.11 اطلاعات بيشتر (More information)
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 روش ساخت (Manufacturing method)
ساخت LLVM/Clang با [17]CMake انجام ميشود.
11.2 كاربرد (Use Case)
برای کامپایل و لینک برنامه، از ++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 این حالت را برای همه آنها فعال میکند.
clang -fsanitize-trap=undefined -fsanitize-recover=all a.c
-fsanitize-trap= و -fsanitize-recover= در غياب يك گزينه -fsanitize= بياثر هستند. در اين حالت هيچ هشدار گزينه بلااستفاده نيز نمايش داده نميشود.
11.3 بررسيهاي در دسترس (Available reviews)
بررسيهاي زير در دسترس هستند:
- -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 غيرفعالسازي ابزارگذاري براي الگوهاي متداول سرريز (Disabling instrumentation for common overflow patterns)
برخی الگوهای کدنویسی وابسته به سرريز یا مستعد سرريز بیش از حد گزارش تولید میکنند و باعث نویز زیاد برای 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 سركوب خطاها (Suppression of errors)
ابزار 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 سركوب خطاها در كدِ بازكامپايل شده (Suppressing errors in recompiled code)
ابزار UndefinedBehaviorSanitizer از انواع src و fun در فهرست موارد خاص Sanitizer پشتیبانی میکند؛ این موارد را میتوان برای سرکوب گزارشهای خطا در فایلهای مبدأ یا توابع مشخص شده به کار برد.
11.12 سركوبهاي زمان اجرا (Runtime suppressions)
گاهی میتوان بدون نیاز به بازکامپایل کردن کد، گزارشهای خطای UBSan را برای فایلها، توابع یا کتابخانههای مشخص سرکوب کرد. برای این کار باید مسیر فایل سرکوب (suppression file) را در متغیر محیطی 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 ملاحظات امنيتي (Security considerations)
زمان اجرای UndefinedBehaviorSanitizer (UBSan) برای مقاصد آزمون طراحی شده است و استفاده از آن در محیط تولیدی باید از منظر امنیتی با دقت بررسی شود، زیرا ممکن است امنیت اجرای نهایی را تضعیف کند. برای کاربردهای حساس به امنیت، استفاده از Minimal Runtime یا حالت trap mode برای تمام بررسیها توصیه میشود.
11.14 پلتفرمهاي پشتيباني شده (Supported platforms)
UndefinedBehaviorSanitizer بر روي سیستمعاملهای زير پشتيباني ميشود:
- Android
- Linux
- NetBSD
- FreeBSD
- OpenBSD
- macOS
- Windows
کتابخانه زمان اجرا نسبتاً قابلانتقال و مستقل از پلتفرم است. اگر سیستمعاملی که نیاز دارید در فهرست بالا وجود ندارد، ممکن است UndefinedBehaviorSanitizer از قبل روی آن کار کند یا با اندکی تلاش برای انتقال، قابل استفاده باشد.
11.15 وضعيت كنوني (Current status)
UndefinedBehaviorSanitizer از نسخه ۳.۳ LLVM به بعد روی برخی پلتفرمها در دسترس است. مجموعه آزمون آن در سیستم ساخت CMake یکپارچه شده و با دستور check-ubsan قابل اجرا است.
11.16 پيكربندي اضافي (Additional configuration)
UndefinedBehaviorSanitizer برای هر بررسی، دادههای ایستا اضافه میکند مگر اینکه در حالت trap باشد. این دادهها شامل نام کامل فایل هستند. گزینه -fsanitize-undefined-strip-path-components=N برای کاهش اجزای مسیر قابل استفاده است. چنانچه N مثبت باشد، از مسیر فایل خروجی، N جزء ابتدایی مسیر حذف میشود. اگر N منفی باشد، فقط N جزء پایانی مسیر حفظ میشود.
11.17 مثال (Example)
براي فايلي با نام /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 اطلاعات بيشتر
[19] From Oracle blog, including a discussion of error messages: Improving Application Security with UndefinedBehaviorSanitizer (UBSan) and GCC
[20] From LLVM project blog: What Every C Programmer Should Know About Undefined Behavior
[21] From John Regehr’s Embedded in Academia blog: A Guide to Undefined Behavior in C and C++
پاورقی:
[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
[7] https://github.com/google/sanitizers/wiki/ThreadSanitizerFlags
[8] https://clang.llvm.org/docs/ThreadSanitizer.html
[9] https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManua
[10] https://llvm.org/docs/CMake.html
[11] https://clang.llvm.org/docs/MemorySanitizer.html#msan-origins
[12] 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