Linux+Windows: 程序崩潰時,在 C++ 代碼中,如何獲取函數(shù)調(diào)用棧信息
一、前言
程序在執(zhí)行過程中 crash 是非常嚴重的問題,一般都應該在測試階段排除掉這些問題,但是總會有漏網(wǎng)之魚被帶到 release 階段。
因此,程序的日志系統(tǒng)需要偵測這種情況,在代碼崩潰的時候獲取函數(shù)調(diào)用棧信息,為 debug 提供有效的信息。
這篇文章的理論知識很少,直接分享 2 段代碼:在 Linux 和 Windows 這 2 個平臺上,如何用C++ 來捕獲函數(shù)調(diào)用棧里的信息。
二、Linux 平臺
1. 注冊異常信號的處理函數(shù)
需要處理哪些異常信號
- #include <execinfo.h>
- #include <cxxabi.h>
- #include <signal.h>
- const std::map<int, std::string> Signals = {
- {SIGINT, "SIGINT"},
- {SIGABRT, "SIGABRT"},
- {SIGFPE, "SIGFPE"},
- {SIGILL, "SIGILL"},
- {SIGSEGV, "SIGSEGV"}
- // 可以添加其他信號
- };
注冊信號處理函數(shù)
- struct sigaction action;
- sigemptyset(&action.sa_mask);
- action.sa_sigaction = &sigHandler;
- action.sa_flags = SA_SIGINFO;
- for (const auto &sigPair : Signals)
- {
- if (sigaction(sigPair.first, &action, NULL) < 0)
- fprintf(stderr, "Error: sigaction failed! \n");
- }
2. 捕獲異常,獲取函數(shù)調(diào)用棧信息
- void sigHandler(int signum, siginfo_t *info, void *ctx)
- {
- const size_t dump_size = 50;
- void *array[dump_size];
- int size = backtrace(array, dump_size);
- char **symbols = backtrace_symbols(array, size);
- std::ostringstream oss;
- for (int i = 0; i < size; ++i)
- {
- char *mangleName = 0;
- char *offsetBegin = 0;
- char *offsetEnd = 0;
- for (char *p = symbols[i]; *p; ++p)
- {
- if ('(' == *p)
- {
- mangleName = p;
- }
- else if ('+' == *p)
- {
- offsetBegin = p;
- }
- else if (')' == *p)
- {
- offsetEnd = p;
- break;
- }
- }
- if (mangleName && offsetBegin && offsetEnd && mangleName < offsetBegin)
- {
- *mangleName++ = '\0';
- *offsetBegin++ = '\0';
- *offsetEnd++ = '\0';
- int status;
- char *realName = abi::__cxa_demangle(mangleName, 0, 0, &status);
- if (0 == status)
- oss << "\tstack dump [" << i << "] " << symbols[i] << " : " << realName << "+";
- else
- oss << "\tstack dump [" << i << "] " << symbols[i] << mangleName << "+";
- oss << offsetBegin << offsetEnd << std::endl;
- free(realName);
- }
- else
- {
- oss << "\tstack dump [" << i << "] " << symbols[i] << std::endl;
- }
- }
- free(symbols);
- oss << std::endl;
- std::cout << oss.str(); // 打印函數(shù)調(diào)用棧信息
- }
三、Windwos 平臺
在 Windows 平臺下的代碼實現(xiàn),參考了國外某個老兄的代碼,如下:
1. 設置異常處理函數(shù)
- #include <windows.h>
- #include <dbghelp.h>
- SetUnhandledExceptionFilter(exceptionHandler);
2. 捕獲異常,獲取函數(shù)調(diào)用棧信息
- void exceptionHandler(LPEXCEPTION_POINTERS info)
- {
- CONTEXT *context = info->ContextRecord;
- std::shared_ptr<void> RaiiSysCleaner(nullptr, [&](void *) {
- SymCleanup(GetCurrentProcess());
- });
- const size_t dumpSize = 64;
- std::vector<uint64_t> frameVector(dumpSize);
- DWORD machine_type = 0;
- STACKFRAME64 frame = {};
- frame.AddrPC.Mode = AddrModeFlat;
- frame.AddrFrame.Mode = AddrModeFlat;
- frame.AddrStack.Mode = AddrModeFlat;
- #ifdef _M_IX86
- frame.AddrPC.Offset = context->Eip;
- frame.AddrFrame.Offset = context->Ebp;
- frame.AddrStack.Offset = context->Esp;
- machine_type = IMAGE_FILE_MACHINE_I386;
- #elif _M_X64
- frame.AddrPC.Offset = context->Rip;
- frame.AddrFrame.Offset = context->Rbp;
- frame.AddrStack.Offset = context->Rsp;
- machine_type = IMAGE_FILE_MACHINE_AMD64;
- #elif _M_IA64
- frame.AddrPC.Offset = context->StIIP;
- frame.AddrFrame.Offset = context->IntSp;
- frame.AddrStack.Offset = context->IntSp;
- machine_type = IMAGE_FILE_MACHINE_IA64;
- frame.AddrBStore.Offset = context.RsBSP;
- frame.AddrBStore.Mode = AddrModeFlat;
- #else
- frame.AddrPC.Offset = context->Eip;
- frame.AddrFrame.Offset = context->Ebp;
- frame.AddrStack.Offset = context->Esp;
- machine_type = IMAGE_FILE_MACHINE_I386;
- #endif
- for (size_t index = 0; index < frameVector.size(); ++index)
- {
- if (StackWalk64(machine_type,
- GetCurrentProcess(),
- GetCurrentThread(),
- &frame,
- context,
- NULL,
- SymFunctionTableAccess64,
- SymGetModuleBase64,
- NULL)) {
- frameVector[index] = frame.AddrPC.Offset;
- } else {
- break;
- }
- }
- std::string dump;
- const size_t kSize = frameVector.size();
- for (size_t index = 0; index < kSize && frameVector[index]; ++index) {
- dump += getSymbolInfo(index, frameVector);
- dump += "\n";
- }
- std::cout << dump;
- }
主要是利用了 StackWalk64 這個函數(shù),從地址轉(zhuǎn)換為函數(shù)名稱。
利用以上幾個神器,基本上可以獲取到程序崩潰時的函數(shù)調(diào)用棧信息,定位問題,有如神助!