هل من الممكن التنبؤ تجاوز سعة مكدس في ج على لينكس ؟

StackOverflow https://stackoverflow.com/questions/427209

  •  06-07-2019
  •  | 
  •  

سؤال

هناك بعض الشروط التي يمكن أن تسبب كومة يفيض على x86 نظام لينكس:

  • struct my_big_object[HUGE_NUMBER] على المكدس.المشي من خلال ذلك يسبب في نهاية المطاف SIGSEGV.
  • على alloca() الروتينية (مثل malloc(), ولكن يستخدم كومة تلقائيا يحرر نفسه ، كما يفجر مع SIGSEGV لو كان كبير جدا). تحديث:alloca() ليس رسميا إهمال وأنا أصلا ذكر ؛ هو مجرد تثبيط.

هل هناك طريقة برمجيا الكشف عن إذا المحلية كومة كبيرة بما يكفي معين كائن ؟ أنا أعرف كومة حجم هو قابل للتعديل عن طريق ulimit, لذا لدي أمل هناك طريقة (ومع ذلك غير المحمولة قد يكون).من الناحية المثالية, وأود أن تكون قادرة على القيام بشيء من هذا القبيل:

int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
    char *foo = alloca(object_size);
    do_stuff(foo);
}
else
{
    char *foo = malloc(object_size);
    do_stuff(foo);
    free(foo);
}
هل كانت مفيدة؟

المحلول

يمكنك تحديد مساحة المكدس المتوفرة للعملية من خلال إيجاد حجم مساحة المكدس الخاصة بالعملية ثم طرح الكمية المستخدمة.

ulimit -s

يظهر حجم المكدس على نظام لينكس.للحصول على نهج برمجي، تحقق من جيترليميت().ثم، لتحديد عمق المكدس الحالي، قم بطرح مؤشر إلى أعلى المكدس من واحد إلى الأسفل.على سبيل المثال (لم يتم اختبار الكود):

unsigned char *bottom_of_stack_ptr;

void call_function(int argc, char *argv) {
    unsigned char top_of_stack;
    unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ? 
        &top_of_stack-bottom_of_stack_ptr : 
        bottom_of_stack_ptr-&top_of_stack;

    if( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
        ...
    }
}

int main(int argc, char *argv) {
    unsigned char bottom_of_stack;
    bottom_of_stack_ptr = &bottom_of_stack;
    my_function();
    return 0;
}

نصائح أخرى

إهمال alloca() الروتينية (مثل malloc () ، ولكن يستخدم المكدس ، تلقائيا يحرر نفسه ، كما يفجر مع SIGSEGV لو كان كبير جدا).

لماذا alloca إهمال?

على أية حال, كيف أسرع بكثير في حالة alloca مقابل malloc?(هل يستحق ذلك؟)

و لا يمكنك الحصول على باطل العودة من alloca إذا لم يكن هناك enought الفضاء اليسار ؟ (بنفس الطريقة malloc?)

وعندما رمز الحادث, حيث أنها لا تحطم ؟ هو في alloca أو في doStuff()?

/يوهان

لست متأكدًا مما إذا كان هذا ينطبق على نظام التشغيل Linux، ولكن على نظام التشغيل Windows، من الممكن حدوث انتهاكات للوصول مع عمليات تخصيص المكدس الكبيرة حتى لو نجحوا!

هذا لأنه افتراضيًا، يقوم نظام VMM الخاص بنظام التشغيل Windows بتحديد الصفحات القليلة الأولى (لست متأكدًا من العدد الدقيق) التي تبلغ 4096 بايت من ذاكرة الوصول العشوائي (RAM) على أنها قابلة للصفح (أي صفحات قابلة للصفح).مدعومة بملف ترحيل الصفحات)، لأنها تعتقد أن عمليات الوصول إلى المكدس ستسير بشكل عام إلى الأسفل من الأعلى؛مع اقتراب الوصول أكثر فأكثر من "الحدود" الحالية، يتم وضع علامة على الصفحات السفلية والسفلى على أنها قابلة للصفح.ولكن هذا يعني أن القراءة/الكتابة المبكرة للذاكرة أسفل الجزء العلوي من المكدس ستؤدي إلى انتهاك الوصول حيث لم يتم تخصيص تلك الذاكرة فعليًا بعد!

سوف يقوم alloca() بإرجاع NULL عند الفشل، وأعتقد أن سلوك alloca(0) غير محدد ومتغير للنظام الأساسي.إذا قمت بالتحقق من ذلك قبل do_something()، فلن يتم ضربك أبدًا بـ SEGV.

لدي بضعة أسئلة:

  1. لماذا، لماذا، هل تحتاج إلى شيء كبير على المكدس؟الحجم الافتراضي في معظم الأنظمة هو 8 ميجا، فهل لا يزال هذا صغيرًا جدًا؟
  2. إذا كانت الوظيفة التي تستدعي كتل alloca()، ستحمي نفس مقدار الكومة عبر mlock() / mlockall() مما يضمن أداء وصول قريب من نفس الأداء (أي."لا تستبدلني يا أخي!") مع مرور الوقت؟إذا كنت تستخدم برنامج جدولة 'rt' أكثر عدوانية، فمن المستحسن الاتصال به على أي حال.

السؤال مثير للاهتمام ولكنه يثير الدهشة.إنه يرفع الإبرة على مقياس الربط المربع الدائري.

أنت لا تقول الكثير عن سبب رغبتك في التخصيص على المكدس، ولكن إذا كان نموذج ذاكرة المكدس هو الجذاب، فيمكنك تنفيذ تخصيص المكدس على الكومة أيضًا.خصص جزءًا كبيرًا من الذاكرة في بداية البرنامج واحتفظ بمجموعة من المؤشرات التي تتوافق مع الإطارات الموجودة في المكدس العادي.كل ما عليك فعله هو أن تتذكر أن تنبثق مؤشر المكدس الخاص عندما تعود الوظيفة.

العديد من المترجمين، على سبيل المثال افتح واتكوم C/C++, ، يدعم وظيفة Stackavail () التي تتيح لك القيام بذلك بالضبط

يمكنك استخدام GNU libsigsegv ل مقبض خطأ في الصفحة، بما في ذلك الحالات التي يحدث فيها تجاوز سعة المكدس (من موقعه على الويب):

في بعض التطبيقات، يقوم معالج تجاوز سعة المكدس بإجراء بعض عمليات التنظيف أو يقوم بإعلام المستخدم ثم يقوم بإنهاء التطبيق على الفور.في التطبيقات الأخرى، يعود معالج تجاوز سعة المكدس إلى نقطة مركزية في التطبيق.تدعم هذه المكتبة كلا الاستخدامين.في الحالة الثانية، يجب على المعالج التأكد من استعادة قناع الإشارة العادي (لأن العديد من الإشارات يتم حظرها أثناء تنفيذ المعالج)، ويجب أيضًا استدعاء sigsegv_leave_handler() لنقل التحكم؛عندها فقط يمكنه أن يبتعد.

وظيفة التخصيص هي لا إهمال.ومع ذلك، فهو غير موجود في POSIX ويعتمد أيضًا على الآلة والمترجم.تشير صفحة Linux man الخاصة بـ alloca إلى أنه "بالنسبة لتطبيقات معينة، يمكن أن يؤدي استخدامه إلى تحسين الكفاءة مقارنة باستخدام malloc، وفي حالات معينة يمكن أيضًا تبسيط عملية إلغاء تخصيص الذاكرة في التطبيقات التي تستخدم longjmp() أو siglongjmp().وبخلاف ذلك، لا يتم تشجيع استخدامه."

تقول الصفحة أيضًا أنه "لا توجد إشارة خطأ إذا تعذر توسيع إطار المكدس.ومع ذلك، بعد فشل التخصيص، من المحتمل أن يتلقى البرنامج SIGSEGV."

تم ذكر أداء malloc بالفعل في Stackoverflow بودكاست #36.

(أعلم أن هذه ليست إجابة مناسبة لسؤالك، لكنني أعتقد أنها قد تكون مفيدة على أي حال.)

حتى لو لم تكن هذه إجابة مباشرة على سؤالك، أتمنى أن تكون على علم بوجود valgrind - أداة رائعة لاكتشاف مثل هذه المشكلات في وقت التشغيل على نظام التشغيل Linux.

فيما يتعلق بمشكلة المكدس، يمكنك محاولة تخصيص الكائنات ديناميكيًا من تجمع ثابت يكتشف هذه التجاوزات.باستخدام معالج ماكرو بسيط، يمكنك تشغيل هذا في وقت التصحيح، مع تشغيل التعليمات البرمجية الحقيقية في وقت الإصدار، وبالتالي تعرف (على الأقل بالنسبة للسيناريوهات التي تنفذها) أنك لا تأخذ الكثير. إليك المزيد من المعلومات والرابط إلى تنفيذ العينة.

ليس هناك طريقة لطيفة يمكنني التفكير بها.ربما يكون ذلك ممكنًا باستخدام getrlimit() (المقترح من قبل) وبعض حسابات المؤشر؟لكن اسأل نفسك أولاً إذا كنت تريد هذا حقًا.

void *closeToBase;

main () {
  int closeToBase;
  stackTop = &closeToBase;
}

int stackHasRoomFor(int bytes) {
  int currentTop;
  return getrlimit(...) - (¤tTop  - closeToBase) > bytes + SomeExtra;
}

أنا شخصياً لن أفعل هذا.قم بتخصيص أشياء كبيرة على الكومة، ولم يكن المكدس مخصصًا لها.

يتم تحديد نهاية منطقة المكدس ديناميكيًا بواسطة نظام التشغيل.على الرغم من أنه يمكنك العثور على الحدود "الثابتة" للمكدس من خلال النظر إلى مناطق الذاكرة الظاهرية (VMAs) بطريقة تعتمد بشكل كبير على نظام التشغيل (راجع ملفات stackvma* في libsigsegv/src/)، سيكون عليك أيضًا أن تفكر فيه

نعتذر إذا كان هذا واضحًا، ولكن يمكنك بسهولة كتابة دالة لاختبار حجم تخصيص مكدس معين بمجرد تجربة التخصيص (بهذا الحجم) والتقاط استثناء تجاوز سعة المكدس.إذا أردت، يمكنك وضعها في دالة، مع بعض العمليات الحسابية المحددة مسبقًا لمكدس الدالة.على سبيل المثال:

bool CanFitOnStack( size_t num_bytes )
{
    int stack_offset_for_function = 4; // <- Determine this
    try
    {
        alloca( num_bytes - stack_offset_for_function );
    }
    catch ( ... )
    {
        return false;
    }

    return true;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top