C SIGSEGV Handler & mprotect
سؤال
أقوم بإنشاء برنامج يستخدم mprotect () لتقييد كتلة الذاكرة من الوصول. عند طلب الذاكرة ، يتم إلقاء sigsegv والتي أستمع إليها باستخدام مكالمة Signal ().
بمجرد اكتشاف SIGSEGV ، أحتاج إلى الوصول إلى المؤشر بطريقة ما إلى الذاكرة التي تم طلبها (التي ألقى الخطأ) وحجم المقطع المطلوب. هل هذا ممكن؟
void fifoSigHandler(){
// Needs to only remove protection from requested block of virtual memory
mprotect(fifoVm,(size_t)fifoVm_size,PROT_WRITE);
printf("Caught Seg Fault");
}
void fifo_init(void* vm, int vm_size, int n_frames, int page_size)
{
fifoVm = vm;
fifoVm_size = vm_size;
fifoFrames = n_frames;
fifoPageSize = page_size;
mprotect(fifoVm,(size_t)fifoVm_size,PROT_NONE);
signal(SIGSEGV, fifoSigHandler);
}
بالإضافة إلى ذلك ، هل هناك طريقة لتحديد مستوى mProtect () تم تعيين كتلة من الذاكرة حاليًا (prot_none ، prot_read ، إلخ.)؟
المحلول
عليك أن تستخدم sigaction
مع SA_SIGINFO
بدلاً من signal
لتأسيس معالجك ، وبعد ذلك سوف يتم الاتصال بك بمعلومات مفيدة في أ siginfo_t
, ، بما فيها si_addr
.
si_addr
, ، كما هو موضح في sigaction
(2) ، سوف تحتوي على العنوان. بالنسبة للطول ، حسنًا ، أنت محظوظ إلا إذا كنت على استعداد لتحليل التعليمات. أفضل ما يمكنك القيام به هو اتخاذ إجراء للصفحة المبلغ عنها في si_addr
, ، ثم إذا لم يكن ذلك كافيًا ، فستحصل على إشارة أخرى قريبًا بما فيه الكفاية. على الأقل ، هكذا فعلنا الأشياء في ObjectStore.
نصائح أخرى
أنت تبحث عن libsigsegv
http://libsigsegv.sourceforge.net/
لكن احذر من ذلك الاتصال mprotect
هو فقط آمن للإشارة في Linux ، قد لا تدعم أنظمة POSIX الأخرى هذا.
أخشى أنه في Linux ، فإن الطريقة الوحيدة للحصول على بتات حماية الذاكرة هي القراءة في /proc/$pid/meminfo
على ملاحظة جانبية (Linux فقط): إذا كنت قلقًا بشأن استهلاك الذاكرة وتعتزم تمكين صفحات رسم خرائط أكبر واحدًا تلو الآخر ، فأنا أنصح بإنشاء رسم الخرائط الخاص بك باستخدام mmap
مع MAP_NORESERVE
في هذه الحالة ، ستحصل على رسم خرائط لصفحات النسخ المملوءة بالصفر والتي ستخصص ذاكرة الوصول العشوائي المادية في الكتابة الأولى. MAP_NORESERVE
يرشد kernel بعدم دعم ذاكرتك بمساحة المبادلة مما يتيح لك تخصيص ما يصل إلى 64 تيرابايت من مساحة العنوان الظاهري. الجانب السلبي هو أنه إذا نفدت من الذاكرة ، فيمكن أن تحدث أشياء فظيعة (قاتل OOM).
الخطوة 1: init sigaction
:
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
sigemptyset(&act.sa_mask);
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO | SA_ONSTACK;
الخطوة 2: اصنع هذا sigaction
يتعامل SIGSEGV
:
sigaction(SIGSEGV, &act, NULL);
(اختياري) الخطوه 3: اجعلها تتعامل مع إشارات الذاكرة الأخرى أيضًا:
sigaction(SIGBUS, &act, NULL);
sigaction(SIGTRAP, &act, NULL);
أضف معالجة الخطأ حسب الضرورة
الخطوة 4: تحديد وظيفة المعالج:
void handler(int signal, siginfo_t* siginfo, void* uap) {
printf("Attempt to access memory at address %p\n",
siginfo->si_addr);
#ifdef LINUX_64BIT
printf("Instruction pointer: %p\n",
(((ucontext_t*)uap)->uc_mcontext.gregs[16]));
#elif LINUX_32BIT
printf("Instruction pointer: %p\n",
(((ucontext_t*)uap)->uc_mcontext.gregs[14]));
#endif
}
يمكنك الرجوع إلى صفحات الرجل ucontext_t
و siginfo_t
للحصول على بيانات أكثر إثارة للاهتمام يمكن أن يستخرج معالجك.