كيفية إنشاء تتبع مكدس تلقائيًا عند تعطل برنامجي

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

سؤال

أنا أعمل على Linux مع مترجم دول مجلس التعاون الخليجي.عندما يتعطل برنامج C++ الخاص بي، أرغب في إنشاء تتبع مكدس تلقائيًا.

يتم تشغيل برنامجي من قبل العديد من المستخدمين المختلفين ويعمل أيضًا على Linux وWindows وMacintosh (جميع الإصدارات يتم تجميعها باستخدام gcc).

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

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

المحلول

بالنسبة لنظام التشغيل Linux ونظام التشغيل Mac OS X، إذا كنت تستخدم gcc، أو أي مترجم يستخدم glibc، فيمكنك استخدام وظائف backtrace() في execinfo.h لطباعة تتبع المكدس والخروج بأمان عندما تحصل على خطأ تجزئة.يمكن العثور على الوثائق في دليل ليبك.

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

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


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

التجميع مع -g -rdynamic يحصل على معلومات الرمز في مخرجاتك، والتي يمكن لـ glibc استخدامها لإنشاء تتبع مكدس لطيف:

$ gcc -g -rdynamic ./test.c -o test

تنفيذ هذا يحصل على هذا الإخراج:

$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]

يُظهر هذا وحدة التحميل والإزاحة والوظيفة التي جاء منها كل إطار في المكدس.هنا يمكنك رؤية معالج الإشارة أعلى المكدس، ووظائف libc من قبل main بالإضافة إلى main, foo, bar, ، و baz.

نصائح أخرى

لينكس

في حين أن استخدام وظائف backtrace() في execinfo.h لطباعة تتبع المكدس والخروج بأمان عندما تحصل على خطأ تجزئة تم اقتراحه بالفعل, ، لا أرى أي ذكر للتعقيدات اللازمة لضمان أن التتبع الخلفي الناتج يشير إلى الموقع الفعلي للخطأ (على الأقل بالنسبة لبعض البنيات - x86 وARM).

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

شفرة

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 struct ucontext   *uc_link;
 stack_t           uc_stack;
 struct sigcontext uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)\n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

انتاج |

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

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

من المهم ملاحظة أن المثال الذي قدمته تم تطويره/اختباره على Linux لنظام التشغيل x86.لقد قمت أيضًا بتنفيذ هذا بنجاح على ARM باستخدام uc_mcontext.arm_pc بدلاً من uc_mcontext.eip.

إليك رابط للمقالة التي تعلمت فيها تفاصيل هذا التنفيذ:http://www.linuxjournal.com/article/6391

إنها أسهل حتى من "man backtrace"، فهناك مكتبة موثقة قليلاً (خاصة بـ GNU) موزعة باستخدام glibc مثل libSegFault.so، والتي أعتقد أنها كتبها Ulrich Drepper لدعم برنامج Catchsegv (انظر "man Catchsegv").

وهذا يعطينا 3 احتمالات.بدلاً من تشغيل "program -o hai":

  1. تشغيل ضمن Catsegv:

    $ catchsegv program -o hai
    
  2. الارتباط مع libSegFault في وقت التشغيل:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. الارتباط مع libSegFault في وقت الترجمة:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
    

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

يمكنك أيضًا التقاط المزيد من الإشارات لتتبعات المكدس باستخدام شيء مثل:

$ export SEGFAULT_SIGNALS="all"       # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT

سيبدو الإخراج كالتالي (لاحظ التتبع الخلفي في الأسفل):

*** Segmentation fault Register dump:

 EAX: 0000000c   EBX: 00000080   ECX:
00000000   EDX: 0000000c  ESI:
bfdbf080   EDI: 080497e0   EBP:
bfdbee38   ESP: bfdbee20

 EIP: 0805640f   EFLAGS: 00010282

 CS: 0073   DS: 007b   ES: 007b   FS:
0000   GS: 0033   SS: 007b

 Trap: 0000000e   Error: 00000004  
OldMask: 00000000  ESP/signal:
bfdbee20   CR2: 00000024

 FPUCW: ffff037f   FPUSW: ffff0000  
TAG: ffffffff  IPOFF: 00000000  
CSSEL: 0000   DATAOFF: 00000000  
DATASEL: 0000

 ST(0) 0000 0000000000000000   ST(1)
0000 0000000000000000  ST(2) 0000
0000000000000000   ST(3) 0000
0000000000000000  ST(4) 0000
0000000000000000   ST(5) 0000
0000000000000000  ST(6) 0000
0000000000000000   ST(7) 0000
0000000000000000

Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]

إذا كنت تريد معرفة التفاصيل الدموية، فإن أفضل مصدر هو للأسف المصدر:يرى http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c والدليل الأصلي الخاص به http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

على الرغم من أ اجابة صحيحة تم توفير وصف لكيفية استخدام GNU libc backtrace() وظيفة1 ولقد قدمت إجابتي الخاصة يصف كيفية التأكد من أن التتبع العكسي من معالج الإشارة يشير إلى الموقع الفعلي للخطأ2, ، لا أرى أي ذكر لذلك التفكيك يتم إخراج رموز C++ من التتبع الخلفي.

عند الحصول على آثار عكسية من برنامج C++، يمكن تشغيل الإخراج من خلاله c++filt1 لتفكيك الرموز أو باستخدام abi::__cxa_demangle1 مباشرة.

  • 1 لينكس ونظام التشغيل العاشرلاحظ أن c++filt و __cxa_demangle خاصة بدول مجلس التعاون الخليجي
  • 2 لينكس

يستخدم مثال C++ Linux التالي نفس معالج الإشارة الذي يستخدمه my إجابة أخرى ويوضح كيف c++filt يمكن استخدامها لفك الرموز.

شفرة:

class foo
{
public:
    foo() { foo1(); }

private:
    void foo1() { foo2(); }
    void foo2() { foo3(); }
    void foo3() { foo4(); }
    void foo4() { crash(); }
    void crash() { char * p = NULL; *p = 0; }
};

int main(int argc, char ** argv)
{
    // Setup signal handler for SIGSEGV
    ...

    foo * f = new foo();
    return 0;
}

انتاج | (./test):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

الناتج المفكك (./test 2>&1 | c++filt):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

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

شفرة:

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific

    std::cerr << "signal " << sig_num 
              << " (" << strsignal(sig_num) << "), address is " 
              << info->si_addr << " from " << caller_address 
              << std::endl << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    array[1] = caller_address;

    char ** messages = backtrace_symbols(array, size);    

    // skip first stack frame (points here)
    for (int i = 1; i < size && messages != NULL; ++i)
    {
        char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;

        // find parantheses and +address offset surrounding mangled name
        for (char *p = messages[i]; *p; ++p)
        {
            if (*p == '(') 
            {
                mangled_name = p; 
            }
            else if (*p == '+') 
            {
                offset_begin = p;
            }
            else if (*p == ')')
            {
                offset_end = p;
                break;
            }
        }

        // if the line could be processed, attempt to demangle the symbol
        if (mangled_name && offset_begin && offset_end && 
            mangled_name < offset_begin)
        {
            *mangled_name++ = '\0';
            *offset_begin++ = '\0';
            *offset_end++ = '\0';

            int status;
            char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);

            // if demangling is successful, output the demangled function name
            if (status == 0)
            {    
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << real_name << "+" << offset_begin << offset_end 
                          << std::endl;

            }
            // otherwise, output the mangled function name
            else
            {
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << mangled_name << "+" << offset_begin << offset_end 
                          << std::endl;
            }
            free(real_name);
        }
        // otherwise, print the whole line
        else
        {
            std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
        }
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}

يمكن أن تكون تستحق النظر إليها جوجل بريكباد, ، ومولد تفريغ الأعطال عبر الأنظمة الأساسية وأدوات لمعالجة عمليات التفريغ.

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

لدى دول مجلس التعاون الخليجي أيضًا بنيتين مدمجتين يمكنهما مساعدتك، ولكن قد يتم أو لا يتم تنفيذهما بالكامل على البنية الخاصة بك، وهما __builtin_frame_address و __builtin_return_address.كلاهما يريد مستوى عدد صحيح فوري (أعني أنه لا يمكن أن يكون متغيرًا).لو __builtin_frame_address بالنسبة لمستوى معين غير الصفر، يجب أن يكون من الآمن الحصول على عنوان المرسل لنفس المستوى.

ulimit -c <value> يضبط الحد الأقصى لحجم الملف الأساسي على نظام التشغيل Unix.بشكل افتراضي، الحد الأقصى لحجم الملف الأساسي هو 0.يمكنك رؤية الخاص بك ulimit القيم مع ulimit -a.

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

تعد ddd وnemiver واجهات أمامية لـ gdb مما يجعل العمل معها أسهل بكثير بالنسبة للمبتدئين.

شكرًا لك متحمسًا للفت انتباهي إلى الأداة المساعدة addr2line.

لقد كتبت نصًا سريعًا وقذرًا لمعالجة مخرجات الإجابة المقدمة هنا:(شكرًا جزيلا لـ jschmier!) باستخدام الأداة المساعدة addr2line.

يقبل البرنامج النصي وسيطة واحدة:اسم الملف الذي يحتوي على الإخراج من الأداة المساعدة jschmier.

يجب أن يطبع الإخراج شيئًا مثل ما يلي لكل مستوى من التتبع:

BACKTRACE:  testExe 0x8A5db6b
FILE:       pathToFile/testExe.C:110
FUNCTION:   testFunction(int) 
   107  
   108           
   109           int* i = 0x0;
  *110           *i = 5;
   111      
   112        }
   113        return i;

شفرة:

#!/bin/bash

LOGFILE=$1

NUM_SRC_CONTEXT_LINES=3

old_IFS=$IFS  # save the field separator           
IFS=$'\n'     # new field separator, the end of line           

for bt in `cat $LOGFILE | grep '\[bt\]'`; do
   IFS=$old_IFS     # restore default field separator 
   printf '\n'
   EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`  
   ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
   echo "BACKTRACE:  $EXEC $ADDR"
   A2L=`addr2line -a $ADDR -e $EXEC -pfC`
   #echo "A2L:        $A2L"

   FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
   FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
   echo "FILE:       $FILE_AND_LINE"
   echo "FUNCTION:   $FUNCTION"

   # print offending source code
   SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
   LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
   if ([ -f $SRCFILE ]); then
      cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
   else
      echo "File not found: $SRCFILE"
   fi
   IFS=$'\n'     # new field separator, the end of line           
done

IFS=$old_IFS     # restore default field separator 

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

$ g++ -g prog.cpp -o prog

بعد ذلك، يمكنك إما تعيين "ulimit -c Unlimited" للسماح له بتفريغ النواة، أو مجرد تشغيل البرنامج داخل gdb.أنا أحب الطريقة الثانية أكثر:

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

آمل أن يساعد هذا.

لقد كنت أبحث في هذه المشكلة لفترة من الوقت.

ودفنت عميقًا في الملف التمهيدي لأدوات أداء Google

http://code.google.com/p/google-perftools/source/browse/trunk/README

يتحدث عن libunwind

http://www.nongnu.org/libunwind/

أحب أن أسمع آراء حول هذه المكتبة.

تكمن المشكلة في -rdynamic في أنه يمكنه زيادة حجم الملف الثنائي بشكل ملحوظ نسبيًا في بعض الحالات

تحتوي بعض إصدارات libc على وظائف تتعامل مع تتبعات المكدس؛قد تكون قادرًا على استخدامها:

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

أتذكر استخدام libunwind منذ وقت طويل للحصول على تتبعات المكدس، ولكن قد لا يكون مدعومًا على نظامك الأساسي.

انسَ تغيير مصادرك وقم ببعض الاختراقات باستخدام وظيفة backtrace() أو وحدات الماكرو - فهذه مجرد حلول سيئة.

كحل يعمل بشكل صحيح، أود أن أنصح:

  1. قم بتجميع برنامجك باستخدام علامة "-g" لتضمين رموز تصحيح الأخطاء في النظام الثنائي (لا تقلق، فلن يؤثر هذا على أدائك).
  2. في نظام التشغيل Linux، قم بتشغيل الأمر التالي:"ulimit -c Unlimited" - للسماح للنظام بإجراء عمليات تفريغ الأعطال الكبيرة.
  3. عندما يتعطل برنامجك، سترى الملف "الأساسي" في دليل العمل.
  4. قم بتشغيل الأمر التالي لطباعة backtrace إلى stdout:gdb -batch -ex "backtrace" ./your_program_exe ./core

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

ulimit -c unlimited

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

يعتبر

يفوز:ماذا عن StackWalk64 http://msdn.microsoft.com/en-us/library/ms680650.aspx

يمكنك استخدام معالج الموت - فئة C++ صغيرة تقوم بكل شيء من أجلك، موثوقة.

ينظر الى:

الرجل 3 التتبع الخلفي

و:

#include <exeinfo.h>
int backtrace(void **buffer, int size);

هذه هي امتدادات جنو.

راجع منشأة Stack Trace في بارِع (بيئة التواصل التكيفية).لقد تمت كتابته بالفعل لتغطية جميع المنصات الرئيسية (والمزيد).المكتبة مرخصة على طراز BSD، لذا يمكنك نسخ/لصق الكود إذا كنت لا تريد استخدام ACE.

يمكنني المساعدة في إصدار Linux:يمكن استخدام الدالة backtrace وbacktrace_symbols وbacktrace_symbols_fd.راجع الصفحات اليدوية المقابلة.

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

يفوز:يفحص هذا من ام اس دي ان

يمكنك أيضًا إلقاء نظرة على كود Chrome الخاص بـ Google لمعرفة كيفية تعامله مع الأعطال.لديها آلية لطيفة للتعامل مع الاستثناءات.

لقد وجدت أن حل @tgamblin غير كامل.لا يمكن التعامل مع تدفق المكدس.أعتقد أنه بشكل افتراضي ، يتم استدعاء معالج الإشارة مع نفس المكدس ويتم إلقاء SIGSEGV مرتين.للحماية، تحتاج إلى تسجيل مكدس مستقل لمعالج الإشارة.

يمكنك التحقق من ذلك باستخدام الكود أدناه.بشكل افتراضي يفشل المعالج.مع الماكرو المحدد STACK_OVERFLOW، كل شيء على ما يرام.

#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <cassert>

using namespace std;

//#define STACK_OVERFLOW

#ifdef STACK_OVERFLOW
static char stack_body[64*1024];
static stack_t sigseg_stack;
#endif

static struct sigaction sigseg_handler;

void handler(int sig) {
  cerr << "sig seg fault handler" << endl;
  const int asize = 10;
  void *array[asize];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, asize);

  // print out all the frames to stderr
  cerr << "stack trace: " << endl;
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  cerr << "resend SIGSEGV to get core dump" << endl;
  signal(sig, SIG_DFL);
  kill(getpid(), sig);
}

void foo() {
  foo();
}

int main(int argc, char **argv) {
#ifdef STACK_OVERFLOW
  sigseg_stack.ss_sp = stack_body;
  sigseg_stack.ss_flags = SS_ONSTACK;
  sigseg_stack.ss_size = sizeof(stack_body);
  assert(!sigaltstack(&sigseg_stack, nullptr));
  sigseg_handler.sa_flags = SA_ONSTACK;
#else
  sigseg_handler.sa_flags = SA_RESTART;  
#endif
  sigseg_handler.sa_handler = &handler;
  assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
  cout << "sig action set" << endl;
  foo();
  return 0;
} 

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

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

لقد وصل الملك الجديد في المدينةhttps://github.com/bombela/backward-cpp

رأس واحد لوضعه في الكود الخاص بك ومكتبة واحدة للتثبيت.

أنا شخصياً أسميها باستخدام هذه الوظيفة

#include "backward.hpp"
void stacker() {

using namespace backward;
StackTrace st;


st.load_here(99); //Limit the number of trace depth to 99
st.skip_n_firsts(3);//This will skip some backward internal function from the trace

Printer p;
p.snippet = true;
p.object = true;
p.color = true;
p.address = true;
p.print(st, stderr);
}

بالإضافة إلى الإجابات المذكورة أعلاه، إليك كيفية جعل نظام التشغيل Debian Linux يقوم بإنشاء ملف تفريغ أساسي

  1. قم بإنشاء مجلد "coredumps" في المجلد الرئيسي للمستخدم
  2. انتقل إلى /etc/security/limits.conf.أسفل السطر ' '، اكتب " soft core غير محدود "، و"root soft core غير محدود" في حالة تمكين عمليات تفريغ النواة للجذر، للسماح بمساحة غير محدودة لعمليات تفريغ الأساسية.
  3. ملحوظة:"* soft core غير محدود" لا يغطي الجذر، ولهذا السبب يجب تحديد الجذر في السطر الخاص به.
  4. للتحقق من هذه القيم، قم بتسجيل الخروج ثم تسجيل الدخول مرة أخرى واكتب "ulimit -a".يجب ضبط "حجم الملف الأساسي" على غير محدود.
  5. تحقق من ملفات .bashrc (المستخدم والجذر إن أمكن) للتأكد من عدم تعيين ulimit هناك.وإلا، سيتم الكتابة فوق القيمة أعلاه عند بدء التشغيل.
  6. افتح /etc/sysctl.conf.أدخل ما يلي في الأسفل:"kernel.core_pattern = /home//coredumps/%e_%t.dump".(%e سيكون اسم العملية، و%t سيكون وقت النظام)
  7. الخروج واكتب "sysctl -P" لتحميل فحص التكوين الجديد/proc/sys/kernel/core_pattern وتحقق من أن هذا يطابق ما كتبته للتو.
  8. يمكن اختبار التفريغ الأساسي عن طريق تشغيل عملية على سطر الأوامر ("&")، ثم إيقافها باستخدام "kill -11".إذا نجح تفريغ النواة، فسترى "(تفريغ النواة)" بعد إشارة خطأ التجزئة.

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

بدءًا من Windows Server 2008 وWindows Vista المزود بحزمة الخدمة Service Pack 1 (SP1)، يمكن تكوين تقرير الأخطاء في Windows (WER) بحيث يتم جمع عمليات تفريغ وضع المستخدم الكاملة وتخزينها محليًا بعد تعطل تطبيق وضع المستخدم.[...]

لم يتم تمكين هذه الميزة بشكل افتراضي.يتطلب تمكين الميزة امتيازات المسؤول.لتمكين الميزة وتكوينها، استخدم قيم التسجيل التالية ضمن HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows تقرير الأخطاء\LocalDumps مفتاح.

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

يتمتع إنشاء تفريغ وضع المستخدم بالمزايا التالية مقارنة بإنشاء تتبع المكدس على العميل:

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

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

قراءة إلزامية، إذا كنت ترغب في تقييم إمكانية تطبيق مقالب صغيرة:

في Linux/unix/MacOSX، استخدم الملفات الأساسية (يمكنك تمكينها باستخدام ulimit أو استدعاء نظام متوافق).على نظام التشغيل Windows، استخدم ميزة الإبلاغ عن الأخطاء من Microsoft (يمكنك أن تصبح شريكًا وتتمكن من الوصول إلى بيانات تعطل التطبيق الخاص بك).

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

يبدو أنه في أحد إصدارات C ++ الأخيرة ظهرت مكتبة لتوفير ما تريده بالضبط، ومن المحتمل أن يكون الكود متعدد المنصات.إنها دفعة::stacktrace, ، والتي يمكنك استخدامها مثل كما هو الحال في عينة دفعة:

#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

const char* backtraceFileName = "./backtraceFile.dump";

void signalHandler(int)
{
    ::signal(SIGSEGV, SIG_DFL);
    ::signal(SIGABRT, SIG_DFL);
    boost::stacktrace::safe_dump_to(backtraceFileName);
    ::raise(SIGABRT);
}

void sendReport()
{
    if (std::filesystem::exists(backtraceFileName))
    {
        std::ifstream file(backtraceFileName);

        auto st = boost::stacktrace::stacktrace::from_dump(file);
        std::ostringstream backtraceStream;
        backtraceStream << st << std::endl;

        // sending the code from st

        file.close();
        std::filesystem::remove(backtraceFileName);
    }
}

int main()
{
    ::signal(SIGSEGV, signalHandler);
    ::signal(SIGABRT, signalHandler);

    sendReport();
    // ... rest of code
}

في Linux تقوم بتجميع الكود أعلاه:

g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

تم نسخ مثال التتبع العكسي من تعزيز الوثائق:

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start

إذا كنت لا تزال ترغب في القيام بذلك بمفردك كما فعلت، فيمكنك الارتباط ضده bfd وتجنب استخدام addr2line كما فعلت هنا:

https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c

وهذا ينتج الإخراج:

[E]        crash.linux.c:170  | crit_err_hdlr                  | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E]        crash.linux.c:171  | crit_err_hdlr                  | signal 11 (Segmentation fault), address is (nil)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top