简介
在开发中我们一定要避免崩溃出现,不过在release发布后的程序中的崩溃也不是完全能掌握。为了能够全面的找到问题所在,输出崩溃时的信息有助于我们排查问题。
下面的代码能提供崩溃时记录错误内容代码行号堆栈等关键信息
主要利用SetUnhandledExceptionFilter
设置异常捕获回调,在回调中处理消息输出。
SetUnhandledExceptionFilter
是 Windows API 中的一个函数,用于设置全局的未捕获异常处理程序。当应用程序中发生未捕获的异常时,此函数指定的处理程序将被调用。
输出样式
测试代码:
int* p = NULL;
*p = 1;
结果:path->user\AppData\Local\"appname"\log\crash_20240825_212443.log
内容:
可以看到输出了具体错误与运行堆栈,与代码文件+行号
Access violation at address 0x7ff7564390a0 (code 0xc0000005)
0x7ff7564390a0: main at D:\Code\tscore2\src\main.cpp:125
0x7ff7565f6ed9: invoke_main at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:79
0x7ff7565f6dbe: __scrt_common_main_seh at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
0x7ff7565f6c7e: __scrt_common_main at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:331
0x7ff7565f6f6e: mainCRTStartup at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp:17
0x7ffaaa7f257d: BaseThreadInitThunk
0x7ffaab66aa58: RtlUserThreadStart
案例代码
//头文件
//Win
#include <windows.h>
#include <dbghelp.h>
//C++
#include <iostream>
//QT
#include <QDir>
#include <QDateTime>
#include <QSysInfo>
#include <QStandardPaths>
#include <QTextStream>
#include <QFile>
#ifdef Q_OS_WIN32
#pragma comment(lib, "dbghelp.lib")
LONG WINAPI unhandledExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
{
std::cerr << "Unhandled exception handler called" << std::endl;
// 打印异常代码
QString crashLog;
switch (exceptionInfo->ExceptionRecord->ExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION:
crashLog += QString("Access violation at address 0x%1 (code 0x%2)\n")
.arg((quintptr)exceptionInfo->ExceptionRecord->ExceptionAddress, 0, 16)
.arg(exceptionInfo->ExceptionRecord->ExceptionCode, 0, 16);
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
crashLog += "Array bounds exceeded\n";
break;
case EXCEPTION_FLT_DENORMAL_OPERAND:
crashLog += "Denormal operand\n";
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
crashLog += "Floating point divide by zero\n";
break;
case EXCEPTION_FLT_INEXACT_RESULT:
crashLog += "Inexact floating point result\n";
break;
case EXCEPTION_FLT_INVALID_OPERATION:
crashLog += "Invalid floating point operation\n";
break;
case EXCEPTION_FLT_OVERFLOW:
crashLog += "Floating point overflow\n";
break;
case EXCEPTION_FLT_STACK_CHECK:
crashLog += "Floating point stack check\n";
break;
case EXCEPTION_FLT_UNDERFLOW:
crashLog += "Floating point underflow\n";
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
crashLog += "Integer divide by zero\n";
break;
case EXCEPTION_INT_OVERFLOW:
crashLog += "Integer overflow\n";
break;
default:
crashLog += QString("Unknown exception at address 0x%1 (code 0x%2)")
.arg((quintptr)exceptionInfo->ExceptionRecord->ExceptionAddress, 0, 16)
.arg(exceptionInfo->ExceptionRecord->ExceptionCode, 0, 16);
break;
}
// 获取堆栈跟踪
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
CONTEXT context = *exceptionInfo->ContextRecord;
SymInitialize(process, NULL, TRUE);
STACKFRAME64 stackFrame;
memset(&stackFrame, 0, sizeof(STACKFRAME64));
#ifdef _M_IX86
DWORD machineType = IMAGE_FILE_MACHINE_I386;
stackFrame.AddrPC.Offset = context.Eip;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Ebp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Esp;
stackFrame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
DWORD machineType = IMAGE_FILE_MACHINE_AMD64;
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Rsp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Rsp;
stackFrame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
DWORD machineType = IMAGE_FILE_MACHINE_IA64;
stackFrame.AddrPC.Offset = context.StIIP;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.IntSp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrBStore.Offset = context.RsBSP;
stackFrame.AddrBStore.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.IntSp;
stackFrame.AddrStack.Mode = AddrModeFlat;
#endif
while (StackWalk64(machineType, process, thread, &stackFrame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
{
DWORD64 address = stackFrame.AddrPC.Offset;
if (address == 0)
{
break;
}
// 获取符号信息
DWORD64 displacement = 0;
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
if (SymFromAddr(process, address, &displacement, symbol))
{
crashLog += QString("0x%1: %2").arg(address, 0, 16).arg(symbol->Name);
// 获取文件名和行号
IMAGEHLP_LINE64 line;
DWORD lineDisplacement;
memset(&line, 0, sizeof(line));
line.SizeOfStruct = sizeof(line);
if (SymGetLineFromAddr64(process, address, &lineDisplacement, &line))
{
crashLog += QString(" at %1:%2\n").arg(line.FileName).arg(line.LineNumber);
}
else
{
crashLog += "\n";
}
}
else
{
crashLog += QString("0x%1: [unknown function]\n").arg(address, 0, 16);
}
}
SymCleanup(process);
// 创建新的日志文件
QString logDirPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/log";
QDir logDir(logDirPath);
if (!logDir.exists()) {
logDir.mkpath(logDirPath);
}
QString logFilePath = logDir.filePath(QString("crash_%1.log").arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss")));
QFile logFile(logFilePath);
if (!logFile.open(QFile::WriteOnly | QFile::Text)) {
std::cerr << "Can't open file to write: " << qPrintable(logFile.errorString()) << std::endl;
return EXCEPTION_EXECUTE_HANDLER;
}
QTextStream logStream(&logFile);
logStream << crashLog << endl;
logStream.flush();
logFile.flush();
logFile.close();
std::cerr << "Crash log written to file:: " << qPrintable(logFilePath) << std::endl;
// 返回值表示是否继续执行
// 如果返回值为 EXCEPTION_CONTINUE_SEARCH,则继续查找异常处理程序
// 如果返回值为 EXCEPTION_CONTINUE_EXECUTION,则忽略异常并继续执行
// 如果返回值为 EXCEPTION_EXECUTE_HANDLER,则执行当前异常处理程序
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
注意在使用log之前运行下面这段话
//记得在其他地方注册
// 注册未处理异常处理函数
SetUnhandledExceptionFilter(unhandledExceptionHandler);
//example
extern LONG WINAPI unhandledExceptionHandler(EXCEPTION_POINTERS* exceptionInfo);
int main(int argc, char *argv[])
{
SetUnhandledExceptionFilter(unhandledExceptionHandler);
}