程序崩潰時(shí),如何獲取函數(shù)調(diào)用棧信息

一、前言
二、Linux 平臺(tái)
三、Windwos 平臺(tái)
一、前言
程序在執(zhí)行過(guò)程中 crash 是非常嚴(yán)重的問(wèn)題,一般都應(yīng)該在測(cè)試階段排除掉這些問(wèn)題,但是總會(huì)有漏網(wǎng)之魚(yú)被帶到 release 階段。
因此,程序的日志系統(tǒng)需要偵測(cè)這種情況,在代碼崩潰的時(shí)候獲取函數(shù)調(diào)用棧信息,為 debug 提供有效的信息。
這篇文章的理論知識(shí)很少,直接分享 2 段代碼:在 Linux 和 Windows 這 2 個(gè)平臺(tái)上,如何用 C++ 來(lái)捕獲函數(shù)調(diào)用棧里的信息。
二、Linux 平臺(tái)
1. 注冊(cè)異常信號(hào)的處理函數(shù)
需要處理哪些異常信號(hào)
const std::map<int, std::string> Signals = {{SIGINT, "SIGINT"},{SIGABRT, "SIGABRT"},{SIGFPE, "SIGFPE"},{SIGILL, "SIGILL"},{SIGSEGV, "SIGSEGV"}// 可以添加其他信號(hào)};
注冊(cè)信號(hào)處理函數(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 << "+";elseoss << "\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 平臺(tái)
在 Windows 平臺(tái)下的代碼實(shí)現(xiàn),參考了國(guó)外某個(gè)老兄的代碼,如下:
1. 設(shè)置異常處理函數(shù)
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;frame.AddrPC.Offset = context->Eip;frame.AddrFrame.Offset = context->Ebp;frame.AddrStack.Offset = context->Esp;machine_type = IMAGE_FILE_MACHINE_I386;frame.AddrPC.Offset = context->Rip;frame.AddrFrame.Offset = context->Rbp;frame.AddrStack.Offset = context->Rsp;machine_type = IMAGE_FILE_MACHINE_AMD64;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;frame.AddrPC.Offset = context->Eip;frame.AddrFrame.Offset = context->Ebp;frame.AddrStack.Offset = context->Esp;machine_type = IMAGE_FILE_MACHINE_I386;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 這個(gè)函數(shù),從地址轉(zhuǎn)換為函數(shù)名稱(chēng)。
利用以上幾個(gè)神器,基本上可以獲取到程序崩潰時(shí)的函數(shù)調(diào)用棧信息,定位問(wèn)題,有如神助!
評(píng)論
圖片
表情
