نکاتی درباره core dump در لینوکس
مقدمه
بدتربن زمان برای یک برنامهنویس زمانی است که برنامهاش کرش میکند و بسته میشود!
یکی از برنامههای که این اواخر نوشتم یک جمعکننده لاگ مرکزی بود که از تعداد زیادی دستگاههای امنیت شبکه لاگ دریافت میکرد و بعد از پردازش اولیه هر لاگ برای بررسی و جستجو و آمارگیری در یک مخزن داده ذخیره میکرد. نرخی ارسال این لاگها تقریبا بالای ۲۰٬۰۰۰ لاگ در ثانیه است و به دلیل اهمیتی که لاگها دارند کرش کردن برنامه یعنی از دست دادن ۲۰٬۰۰۰ لاگ در ثانیه!
بعد از تکمیل برنامه و انجام تستها، برنامه زیر بار قرار گرفت ولی در محیط واقعی اوضاع به گونهی دیگری پیش میرفت و برنامه بعد از گذشت زمانی کرش میکرد. برای رفع ایرادات برنامه و پایدار کردن برنامه از core dump های برنامه بعد از کرش استفاده کردم و در ادامه قصد دارم نکاتی را در اینباره فهرست کنم شاید زمانی به کمکتان بیاید.
در اکثر توزیعهای لینوکس به صورت پیشفرض برنامهها بعد از کرش کردن core dump تولید نمیکنند. دلیل ایجاد نشدن core dump، تنظیم سایز صفر برای محدودیت آن است. این محدودیت را میتوانید در لینوکس با استفاده از فرمان زیر مشاهده کنید:
$ ulimit -c
در صورتی که مقدار نمایش داده شده توسط این فرمان صفر باشد، core dump ایجاد نمیشود.
تنظیم سایز
برای تنظیم مجدد این سایز برای تولید core dump دو روش وجود دارد.
روش اول استفاده از فرمان ulimit به صورت زیر قبل از اجرای برنامه است.
$ ulimit -c unlimited
با اجرای فرمان فوق حداکثر سایز core dump را نامحدود تنظیم کردهاید. به جای unlimited میتوانید از سایز ماکزیمم دلخواه خود استفاده کنید.
روش دوم استفاده از تابع setrlimit در ابتدای خود برنامه است. نمونه این تابع به صورت زیر است.
int setrlimit(int resource, const struct rlimit *rlim);
پارامتر اول این تابع مشخص کننده منبعی است که این محدودیتها برایش تنظیم میشود و در این کاربرد بایستی مقدار RLIMIT_CORE باشد. پارامتر دوم یک ساختار به صورت زیر است و میبایست هر دو متغیر این ساختار برابر با RLIM_INFINITY و یا سایز مورد نظرتان باشد.
struct rlimit { rlim_t rlim_cur; /* Soft limit */ rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */ };
پس در مجموع روش دوم را میتوان به صورت قطعه کد زیر جمعبندی کرد.
struct rlimit core_limit; core_limit.rlim_cur = RLIM_INFINITY; core_limit.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_CORE, &core_limit) < 0) { /* ERROR */ }
برای مطالب بیشتر در مورد این تابع، کاربردهای دیگرش و همچنین کدهای خطای آن به مستندات لینوکس خود مراجعه کنید.
$ man setrlimit
تغییر نام و مکان
برای تمامی برنامهها فایل core dump در کنار برنامه و با نام core ایجاد میشود. گاهی موارد نیاز پیدا میکنید که نام core dumo حاوی اطلاعات بیشتری شامل نام فایل اجرایی، زمان کرش کردن، سیگنال کرش و دیگر اطلاعات باشد. گاهی نیز احتیاج پیدا میکنید که تمامی core dump های برنامههای مختلف به جای مکان جاری برنامهاجرایی در یک مکان مشخصی ایجاد شوند (به عنوان مثال در /var/coredumps/).
برای این کار باید با core pattern آشنا شوید. core pattern در لینوکس بیان کننده الگوی نام core dump است. برای اینکه ببینید core pattern کنونی سیستمتان چیست فرمان زیر را اجرا نمایید.
$ cat /proc/sys/kernel/core_pattern
در این الگو علاوه بر کاراکترهای معمولی برای تعیین نام ثابت، از کاراکترهای الگویی زیر نیز میتوانید استفاده کنید.
- %p (PID برنامه کرش کرده)
- %t (زمان ایجاد core dump)
- %e (نام برنامه کرش کرده)
$ echo '%e.%t.core' > /proc/sys/kernel/core_pattern
و همچنین با اجرای فرمان زیر میتوانید همان الگو را تنظیم نمایید با این تفاوت که فایلهای core dump در یک مسیر مشخص ذخیره شوند.
$ echo '/var/coredumps/%e.%t.core' > /proc/sys/kernel/core_pattern
دقت نمایید که برای اجرای این فرامین نیازمند سطح دسترسی root هستید.
همچنین میتوانید به جای ذخیره کردن core dump، فایل آن را به یک برنامه دیگر ارسال نمایید که در این مطلب نمیگنجد اما میتوانید نحوه انجام این کار و همچنین کاراکترهای الگویی دیگر و مطالب بیشتر درباره core_pattern را در مستندات لینوکس خود مطالعه نمایید.
$ man core
کشف محل ایراد
بعد از ایجاد core dump از برنامه، نوبت به استفاده از اطلاعات dump شده است. یکی از این اطلاعات backtrace برنامه از محل کرش کردن به قبل است که کمک فراوانی به پیدا کردن محل ایراد و احتمالا رفع آن میکند. برای این کار باید از یک دیباگر استفاده کرد مثلا gdb.
چنانچه gdb را به همراه فایل اجرایی و core dump ایجاد شده اجرا کنید، فرمان bt اطلاعات مربوط به backtrace را در اختیارتان قرار میدهد.
$ gdb <executable-file> <core-dump> (gdb) bt
نمونه مثال
برنامه بسیار ساده و با ایراد زیر را در فایل a.cpp بنویسید:
void fn2(int a, int b) { int *p = 0; *p = a + b; } void fn1(int a) { fn2(a, 20); } int main() { fn1(10); return 0; }
$ g++ -g a.cpp -o a
و بعد از ساخت برنامه، فرمان ulimit را اجرا نمایید (چنانچه قبلا این کار را نکردهاید) و بعد از آن برنامه را اجرا کنید.
$ ulimit -c unlimited
$ ./a Segmentation fault (core dumped)
همانطور که خواهید دید بعد از کرش کردن برنامه وابسته به تنظیمات core_pattern فایل core dump ایجاد میشود. اکنون این برنامه را با دیباگر gdb بررسی میکنیم. (بخشهایی از خروجی gdb که نیازی نداشتیم در خروجی زیر حذف شدهاند)
$ gdb a core GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04 ... Core was generated by `./a'. Program terminated with signal 11, Segmentation fault. #0 0x080483cc in fn2 (a=10, b=20) at a.cpp:4 4 *p = a + b; (gdb) bt #0 0x080483cc in fn2 (a=10, b=20) at a.cpp:4 #1 0x080483e9 in fn1 (a=10) at a.cpp:9 #2 0x080483fd in main () at a.cpp:14
همانطور که میبینید بعد از اجرا کردن دیباگر، خطی از برنامه که باعث این کرش شده است مشخص شده و بعد از اجرای فرمان bt فهرست فراخوانی توابع از ابتدا تا محل کرش آورده شده است.
این اطلاعات سرنخهای خوبی برای رفع ایرادات برنامه به دست میدهند.
مراجع
- ۹۲/۰۹/۰۸