c++ exception - 如何在我的gcc C ++程序崩潰時自動生成堆棧跟踪





stack trace (24)


As a Windows-only solution, you can get the equivalent of a stack trace (with much, much more information) using WER . With just a few registry entries, it can be set up to collect user-mode dumps :

Starting with Windows Server 2008 and Windows Vista with Service Pack 1 (SP1), Windows Error Reporting (WER) can be configured so that full user-mode dumps are collected and stored locally after a user-mode application crashes. [...]

This feature is not enabled by default. Enabling the feature requires administrator privileges. To enable and configure the feature, use the following registry values under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps key.

You can set the registry entries from your installer, which has the required privileges.

Creating a user-mode dump has the following advantages over generating a stack trace on the client:

  • It's already implemented in the system. You can either use WER as outlined above, or call MiniDumpWriteDump yourself, if you need more fine-grained control over the amount of information to dump. (Make sure to call it from a different process.)
  • Way more complete than a stack trace. Among others it can contain local variables, function arguments, stacks for other threads, loaded modules, and so on. The amount of data (and consequently size) is highly customizable.
  • No need to ship debug symbols. This both drastically decreases the size of your deployment, as well as makes it harder to reverse-engineer your application.
  • Largely independent of the compiler you use. Using WER does not even require any code. Either way, having a way to get a symbol database (PDB) is very useful for offline analysis. I believe GCC can either generate PDB's, or there are tools to convert the symbol database to the PDB format.

Take note, that WER can only be triggered by an application crash (ie the system terminating a process due to an unhandled exception). MiniDumpWriteDump can be called at any time. This may be helpful if you need to dump the current state to diagnose issues other than a crash.

Mandatory reading, if you want to evaluate the applicability of mini dumps:

當我的C ++程序崩潰時,我希望它自動生成一個堆棧跟踪。

我的程序由許多不同的用戶運行,它也運行在Linux,Windows和Macintosh上(所有版本都使用gcc )。

我希望我的程序能夠在崩潰時生成堆棧跟踪,並且在用戶下次運行堆棧跟踪時,它會問他們是否可以將堆棧跟踪發送給我,以便我可以跟踪問題。 我可以處理髮送給我的信息,但我不知道如何生成跟踪字符串。 有任何想法嗎?




忘記更改源代碼並使用backtrace()函數或宏進行一些攻擊 - 這些只是很差的解決方案。

作為一個正常工作的解決方案,我會建議:

  1. 用“-g”標誌編譯你的程序,將調試符號嵌入到二進製文件中(不要擔心這不會影響你的性能)。
  2. 在linux上運行下一個命令:“ulimit -c unlimited” - 允許系統進行大崩潰轉儲。
  3. 當你的程序崩潰時,在工作目錄中你會看到文件“core”。
  4. 運行下一個命令將回溯打印到stdout:gdb -batch -ex“backtrace”./your_program_exe ./core

這將以可讀的方式打印程序的適當可讀回溯(使用源文件名和行號)。 此外,這種方法可以讓您自由地自動化您的系統:有一個簡短的腳本來檢查進程是否創建了核心轉儲,然後通過電子郵件將回溯發送給開發人員,或將其記錄到某個日誌記錄系統中。




請注意,一旦生成核心文件,您需要使用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 ...

我希望這有幫助。




In addition to above answers, here how you make Debian Linux OS generate core dump

  1. Create a “coredumps” folder in the user's home folder
  2. Go to /etc/security/limits.conf. Below the ' ' line, type “ soft core unlimited”, and “root soft core unlimited” if enabling core dumps for root, to allow unlimited space for core dumps.
  3. NOTE: “* soft core unlimited” does not cover root, which is why root has to be specified in its own line.
  4. To check these values, log out, log back in, and type “ulimit -a”. “Core file size” should be set to unlimited.
  5. Check the .bashrc files (user, and root if applicable) to make sure that ulimit is not set there. Otherwise, the value above will be overwritten on startup.
  6. Open /etc/sysctl.conf. Enter the following at the bottom: “kernel.core_pattern = /home//coredumps/%e_%t.dump”. (%e will be the process name, and %t will be the system time)
  7. Exit and type “sysctl -p” to load the new configuration Check /proc/sys/kernel/core_pattern and verify that this matches what you just typed in.
  8. Core dumping can be tested by running a process on the command line (“ &”), and then killing it with “kill -11 ”. If core dumping is successful, you will see “(core dumped)” after the segmentation fault indication.



可能值得一看Google Breakpad ,一款跨平台崩潰轉儲生成器和用於處理轉儲的工具。







新的國王鎮已經到達https://github.com/bombela/backward-cpp

1頭放置在您的代碼和1庫安裝。

我個人使用這個函數來調用它

#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);
}



你可以使用DeathHandler - 小C ++類,它為你做了一切,可靠。




我發現@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;
} 



我可以幫助Linux版本:可以使用函數backtrace,backtrace_symbols和backtrace_symbols_fd。 請參閱相應的手冊頁。







On Linux/unix/MacOSX use core files (you can enable them with ulimit or compatible system call ). On Windows use Microsoft error reporting (you can become a partner and get access to your application crash data).




儘管提供了正確的答案 ,描述瞭如何使用GNU libc backtrace()函數1,並且我提供了自己的答案 ,描述瞭如何確保從信號處理程序的回溯指向故障2的實際位置,沒有看到任何提及的從backtrace輸出的demangling C ++符號。

當從C ++程序獲取回溯時,可以通過c++filt 1運行輸出來對符號進行abi::__cxa_demangle標記或直接使用abi::__cxa_demangle 1

  • 1 Linux和OS X 請注意, c++filt__cxa_demangle是GCC特有的
  • 2 Linux

以下C ++ Linux示例使用與我的其他答案相同的信號處理程序,並演示如何使用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]

Demangled輸出./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]

以下內容基於原始答案中的信號處理程序,並可以替換上例中的信號處理程序,以演示如何使用abi::__cxa_demangle來對符號進行abi::__cxa_demangle 。 這個信號處理程序產生與上例相同的demangled輸出。

代碼

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);
}



If your program crashes, it's the operating system itself that generates crash dump information. If you're using a *nix OS, you simply need to not prevent it from doing so (check out the ulimit command's 'coredump' options).




* nix:你可以截取SIGSEGV (通常這個信號會在崩潰之前產生)並將信息保存到一個文件中。 (除了可以使用gdb進行調試的核心文件)。

win:從msdn檢查。

你也可以看看谷歌的Chrome代碼,看看它如何處理崩潰。 它有一個很好的異常處理機制。




你沒有指定你的操作系統,所以這很難回答。 如果您使用的是基於gnu libc的系統,則可以使用libc函數backtrace()

GCC也有兩個可以幫助你的內置函數,但是你的體系結構可能會或可能不會完全實現,這些是__builtin_frame_address__builtin_return_address 。 兩者都需要立即整數級別(通過立即,我的意思是它不能是一個變量)。 如果給定級別的__builtin_frame_address非零,則應該可以安全地獲取同一級別的返回地址。




ulimit -c unlimited

是一個系統變量,它允許在應用程序崩潰後創建核心轉儲。 在這種情況下無限量。 在同一個目錄中查找名為core的文件。 確保你編譯了啟用了調試信息的代碼!

問候




我在這裡看到很多答案,執行信號處理程序然後退出。 這是要走的路,但請記住一個非常重要的事實:如果您想獲得生成錯誤的核心轉儲,則不能調用exit(status) 。 請改用abort()




對於Linux,我相信Mac OS X,如果您使用gcc或任何使用glibc的編譯器,則可以使用execinfo.h的backtrace()函數打印execinfo.h並在出現分段錯誤時正常退出。 文檔可以在libc手冊中找到。

這裡有一個示例程序,它安裝一個SIGSEGV處理程序,並在它發生段stderr時將一個堆棧跟踪打印到stderr 。 這裡的baz()函數會導致觸發處理程序的segfault:

#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]

這顯示堆棧中每個幀來自的加載模塊,偏移量和函數。 在這裡,您可以看到堆棧頂部的信號處理程序,以及除mainfoobarbaz之外的main函數之前的libc函數。




I forgot about the GNOME tech of "apport", but I don't know much about using it. It is used to generate stacktraces and other diagnostics for processing and can automatically file bugs. It's certainly worth checking in to.




請參閱ACE (適應性通信環境)中的堆棧跟踪功能。 它已經被寫入涵蓋所有主要平台(以及更多)。 庫是BSD風格的許可證,所以如果你不想使用ACE,你甚至可以復制/粘貼代碼。




感謝thermistgeek將我的注意力吸引到addr2line實用程序。

我編寫了一個快速且骯髒的腳本來處理here提供的答案的輸出:(非常感謝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 



它比“人回溯”更容易,還有一個小文檔庫(GNU特有),它以glibc作為libSegFault.so分發,我相信這是由Ulrich Drepper編寫的,用於支持程序catchsegv(請參閱“man catchsegv”)。

這給了我們3種可能性。 而不是運行“程序 - 海”:

  1. 在catchsegv中運行:

    $ 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




這是一個多年的問題,但是非常受歡迎,值得一提的是了解C ++ 11內存模型的絕佳資源。 為了讓這個又一個完整的答案,我沒有意見總結他的談話,但鑑於這是實際編寫標準的人,我認為這是值得關注的談話。

Herb Sutter對Channel9網站上提供的題為“atomic <> Weapons”的C ++ 11內存模型進行了三小時的長談,內容包括第1 部分第2部分 。 這次演講非常具有技術性,涵蓋以下主題:

  1. 優化,競賽和內存模型
  2. 訂購 - 內容:獲取和發布
  3. 排序 - 如何:互斥體,原子和/或柵欄
  4. 對編譯器和硬件的其他限制
  5. 代碼Gen&Performance:x86 / x64,IA64,POWER,ARM
  6. 輕鬆的原子

談話沒有詳細說明API,而是在推理,背景,引擎蓋下和幕後(你是否知道放寬語義只是因為POWER和ARM不支持同步加載而被添加到標準中?)。





c++ gcc crash stack-trace assert