الحصول على تتبعات المكدس على أنظمة Unix تلقائيًا

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

سؤال

ما هي الطرق المتوفرة للحصول على تتبع المكدس تلقائيًا على أنظمة Unix؟لا أقصد مجرد الحصول على ملف أساسي أو إرفاقه بشكل تفاعلي مع GDB، ولكن وجود معالج SIGSEGV الذي يقوم بتفريغ التتبع الخلفي إلى ملف نصي.

نقاط إضافية للميزات الاختيارية التالية:

  • جمع معلومات إضافية في وقت التعطل (على سبيل المثال.ملفات التكوين).
  • أرسل حزمة معلومات الأعطال عبر البريد الإلكتروني إلى المطورين.
  • القدرة على إضافة هذا في dlopenمكتبة إد المشتركة
  • لا تتطلب واجهة المستخدم الرسومية
هل كانت مفيدة؟

المحلول

إذا كنت تستخدم أنظمة مع BSD backtrace الوظائف المتاحة (Linux، OSX 1.5، BSD بالطبع)، يمكنك القيام بذلك برمجيًا في معالج الإشارة الخاص بك.

على سبيل المثال (backtrace رمز مشتق من مثال IBM):

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void sig_handler(int sig)
{
    void * array[25];
    int nSize = backtrace(array, 25);
    char ** symbols = backtrace_symbols(array, nSize);

    for (int i = 0; i < nSize; i++)
    {
        puts(symbols[i]);;
    }

    free(symbols);

    signal(sig, &sig_handler);
}

void h()
{
    kill(0, SIGSEGV);
}

void g()
{
    h();
}

void f()
{
    g();
}

int main(int argc, char ** argv)
{
    signal(SIGSEGV, &sig_handler);
    f();
}

انتاج:

0   a.out                               0x00001f2d sig_handler + 35
1   libSystem.B.dylib                   0x95f8f09b _sigtramp + 43
2   ???                                 0xffffffff 0x0 + 4294967295
3   a.out                               0x00001fb1 h + 26
4   a.out                               0x00001fbe g + 11
5   a.out                               0x00001fcb f + 11
6   a.out                               0x00001ff5 main + 40
7   a.out                               0x00001ede start + 54

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

نصائح أخرى

لعِلمِكَ،

الحل المقترح (باستخدام backtrace_symbols في معالج الإشارة) معطل بشكل خطير.لا تستخدمها -

نعم، سيعمل كل من backtrace وbacktrace_symbols على إنتاج أثر خلفي وترجمته إلى أسماء رمزية، ولكن:

  1. يقوم backtrace_symbols بتخصيص الذاكرة باستخدام malloc وأنت تستخدمها مجانًا لتحريرها - إذا كنت تتعطل بسبب تلف الذاكرة، فمن المحتمل جدًا أن تكون ساحة malloc الخاصة بك تالفة وتتسبب في خطأ مزدوج.

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

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

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

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

راجع للشغل، ترغب في القيام بذلك بشكل صحيح؟يفحص هذا خارج.

هتافات ، جليد.

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

#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <cxxabi.h>

void sig_handler(int sig)
{
    std::stringstream stream;
    void * array[25];
    int nSize = backtrace(array, 25);
    char ** symbols = backtrace_symbols(array, nSize);
    for (unsigned int i = 0; i < size; i++) {
        int status;
        char *realname;
        std::string current = symbols[i];
        size_t start = current.find("(");
        size_t end = current.find("+");
        realname = NULL;
        if (start != std::string::npos && end != std::string::npos) {
            std::string symbol = current.substr(start+1, end-start-1);
            realname = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status);
        }
        if (realname != NULL)
            stream << realname << std::endl;
        else
            stream << symbols[i] << std::endl;
        free(realname);
    }
    free(symbols);
    std::cerr << stream.str();
    std::ofstream file("/tmp/error.log");
    if (file.is_open()) {
        if (file.good())
            file << stream.str();
        file.close();
    }
    signal(sig, &sig_handler);
}

ربما يكون حل Dereks هو الأفضل، ولكن إليك البديل على أي حال:

يتيح لك إصدار Linux kernel الأخير إمكانية توجيه عمليات تفريغ النواة إلى برنامج نصي أو برنامج.يمكنك كتابة برنامج نصي لالتقاط التفريغ الأساسي وجمع أي معلومات إضافية تحتاجها وإعادة كل شيء بالبريد.ومع ذلك، يعد هذا إعدادًا عالميًا، لذا فهو ينطبق على أي برنامج معطّل على النظام.سيتطلب أيضًا حقوق الجذر للإعداد.يمكن تهيئته من خلال الملف /proc/sys/kernel/core_pattern.اضبط ذلك على شيء مثل "| /Home/Myuser/Bin/My-Core-Confler-scriper.

يستخدم الأشخاص في Ubuntu هذه الميزة أيضًا.

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