سؤال

أعلم أنه لا توجد وظيفة C قياسية للقيام بذلك.كنت أتساءل ما هي التقنيات المستخدمة في ذلك على نظامي التشغيل Windows و*nix؟(يعد Windows XP هو نظام التشغيل الأكثر أهمية بالنسبة لي للقيام بذلك الآن.)

هل كانت مفيدة؟

المحلول

لقد استخدمنا هذا لمشاريعنا:

https://www.codeproject.com/kb/threads/stackwalker.aspx

الكود فوضوي بعض الشيء IMHO، لكنه يعمل بشكل جيد.ويندوز فقط.

نصائح أخرى

هناك backtrace() وbacktrace_symbols():

من صفحة الرجل:

     #include <execinfo.h>
     #include <stdio.h>
     ...
     void* callstack[128];
     int i, frames = backtrace(callstack, 128);
     char** strs = backtrace_symbols(callstack, frames);
     for (i = 0; i < frames; ++i) {
         printf("%s\n", strs[i]);
     }
     free(strs);
     ...

إحدى الطرق لاستخدام هذا بطريقة أكثر ملاءمة/OOP هي حفظ نتيجة backtrace_symbols() في مُنشئ فئة الاستثناء.وبالتالي، كلما قمت بطرح هذا النوع من الاستثناءات، يكون لديك تتبع المكدس.وبعد ذلك، ما عليك سوى توفير وظيفة لطباعتها.على سبيل المثال:


class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i 

...


try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

تا دا!

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

بالنسبة لنظام التشغيل Windows، تحقق من StackWalk64() API (أيضًا على نظام التشغيل Windows 32 بت).بالنسبة لنظام التشغيل UNIX، يجب عليك استخدام الطريقة الأصلية لنظام التشغيل للقيام بذلك، أو الرجوع إلى backtrace() الخاص بـ glibc، إذا كان ذلك متاحًا.

ومع ذلك، لاحظ أن استخدام Stacktrace في التعليمات البرمجية الأصلية نادرًا ما يكون فكرة جيدة - ليس لأنه غير ممكن، ولكن لأنك عادةً تحاول تحقيق الشيء الخطأ.

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

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

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

لا توجد طريقة مستقلة عن النظام الأساسي للقيام بذلك.

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

للنوافذ، CaptureStackBackTrace() يعد أيضًا خيارًا يتطلب كود إعداد أقل من جانب المستخدم مقارنةً بـ StackWalk64() يفعل.(أيضًا، بالنسبة لسيناريو مماثل كان لدي، CaptureStackBackTrace() انتهى الأمر بالعمل بشكل أفضل (أكثر موثوقية) من StackWalk64().)

يجب أن تستخدم مكتبة الاسترخاء.

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

قد يعمل أسلوبك أيضًا بشكل جيد إلا إذا قمت بإجراء مكالمة من مكتبة مشتركة.

يمكنك استخدام ال addr2line أمر على Linux للحصول على الوظيفة المصدر/رقم السطر للكمبيوتر الشخصي المقابل.

سولاريس لديه com.pstack الأمر، والذي تم نسخه أيضًا إلى Linux.

هل لي أن أشير إلى مقالتي.إنها مجرد بضعة أسطر من التعليمات البرمجية.

تصحيح أخطاء ما بعد الوفاة

على الرغم من أنني أعاني حاليًا من مشاكل مع تنفيذ x64 لهذا.

على مدى السنوات القليلة الماضية كنت أستخدم libbacktrace الخاص بإيان لانس تايلور.إنها أنظف بكثير من الوظائف الموجودة في مكتبة GNU C والتي تتطلب تصدير جميع الرموز.إنه يوفر فائدة أكبر لتوليد الآثار الخلفية من libunwind.وأخيرًا وليس آخرًا، لم يتم هزيمتها بواسطة ASLR كما هو الحال مع الأساليب التي تتطلب أدوات خارجية مثل addr2line.

كان Libbacktrace في البداية جزءًا من توزيع دول مجلس التعاون الخليجي، ولكنه الآن متاح من قبل المؤلف كمكتبة مستقلة بموجب ترخيص BSD:

https://github.com/ianlancetaylor/libbacktrace

في وقت كتابة هذا التقرير، لم أكن لأستخدم أي شيء آخر إلا إذا كنت بحاجة إلى إنشاء آثار خلفية على منصة لا يدعمها libbacktrace.

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

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