让程序在崩溃时体面的退出之终极解决方案(SEH+Dump+Unhandled Exception Filter)

本文介绍如何使用SEH和SetUnhandledExceptionFilter结合处理程序异常,确保程序崩溃时能优雅退出并生成Dump文件辅助定位问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在我的上篇文章《让程序在崩溃时体面的退出之SEH+Dump文件》我介绍了怎样用SEH加上Dump文件来避免程序的崩溃并在程序崩溃时创建Dump文件来帮助定位出现异常的代码行。可是只有try/except块中try块中的代码出现异常才能被捕捉到,try块外面的代码出现异常,程序照样会崩溃。
        下面用《让程序在崩溃时体面的退出之SEH+Dump文件》文中的代码为例子来说明。

  1. // 创建Dump文件  
  2. //   
  3. void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)  
  4. {  
  5.     // 创建Dump文件  
  6.     //  
  7.     HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);  
  8.   
  9.     // Dump信息  
  10.     //  
  11.     MINIDUMP_EXCEPTION_INFORMATION dumpInfo;  
  12.     dumpInfo.ExceptionPointers = pException;  
  13.     dumpInfo.ThreadId = GetCurrentThreadId();  
  14.     dumpInfo.ClientPointers = TRUE;  
  15.   
  16.     // 写入Dump文件内容  
  17.     //  
  18.     MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);  
  19.   
  20.     CloseHandle(hDumpFile);  
  21. }  
  22.   
  23. // 作为except块中表达式的函数  
  24. //  
  25. LONG CrashHandler(EXCEPTION_POINTERS *pException)  
  26. {     
  27.     // 在这里添加处理程序崩溃情况的代码  
  28.     //  
  29.   
  30.     // 这里以弹出一个对话框为例子  
  31.     //  
  32.     MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);  
  33.   
  34.     // 创建Dump文件  
  35.     //  
  36.     CreateDumpFile(_T("C:\\Test.dmp"), pException);  
  37.   
  38.     return EXCEPTION_EXECUTE_HANDLER;  
  39. }  
  40.   
  41. int _tmain(int argc, _TCHAR* argv[])  
  42. {  
  43.     __try  
  44.     {  
  45.         MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);  
  46.   
  47.         // 除零,人为的使程序崩溃  
  48.         //  
  49.         int i = 13;  
  50.         int j = 0;  
  51.         int m = i / j;  
  52.     }  
  53.     // 捕捉到让程序崩溃的异常时创建Dump文件  
  54.     //  
  55.     __except(CrashHandler(GetExceptionInformation()))  
  56.     {  
  57.         // 这里以弹出一个对话框为例子  
  58.         //  
  59.         MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);  
  60.     }  
  61.   
  62.     MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);  
  63.   
  64.     return 0;  
  65. }  

        编译上面的代码并运行,会依次弹出下面这些对话框,并在C盘创建一个Dump文件Test.dmp。

        如果把上面代码中的main()函数改成下面的样子,运行编译后的程序依然会崩溃。

  1. int _tmain(int argc, _TCHAR* argv[])  
  2. {  
  3.     __try  
  4.     {  
  5.         MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);  
  6.   
  7.         // 除零,人为的使程序崩溃  
  8.         //  
  9.         int i = 13;  
  10.         int j = 0;  
  11.         int m = i / j;  
  12.     }  
  13.     // 捕捉到让程序崩溃的异常时创建Dump文件  
  14.     //  
  15.     __except(CrashHandler(GetExceptionInformation()))  
  16.     {  
  17.         // 这里以弹出一个对话框为例子  
  18.         //  
  19.         MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);  
  20.     }  
  21.   
  22.     // 除零,人为的使程序崩溃  
  23.     //  
  24.     int i = 13;  
  25.     int j = 0;  
  26.     int m = i / j;  
  27.   
  28.     MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);  
  29.   
  30.     return 0;  
  31. }  

        这种情况在实际编程中是很有可能出现的,毕竟我们不可能事先预计到所有可能出现导致程序崩溃的情况,并把这些代码放到try/except块中。那么对于这些不可预知的异常该怎么办呢?这就要用到我那篇《让程序在崩溃时体面的退出之Unhandled Exception》中的方法:用Windows API中的SetUnhandledExceptionFilter设置一个回调函数来处理这些无法预料的异常。下面是在上面的例子代码上修改后的代码。其中函数CreateDumpFile没有任何变化。

  1. // 得到当前时间  
  2. //  
  3. wstring GetPresentTime()  
  4. {  
  5.      SYSTEMTIME time;  
  6.      GetLocalTime(&time);   
  7.   
  8.      wchar_t wszTime[128];  
  9.      swprintf_s(wszTime, _T("%04d-%02d-%02d %02d-%02d-%02d-%03d"), time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);  
  10.   
  11.      return wstring(wszTime);  
  12. }  
  13.   
  14. // 处理异常的回调函数  
  15. //  
  16. LONG CrashHandler(EXCEPTION_POINTERS *pException)  
  17. {     
  18.     // 在这里添加处理程序崩溃情况的代码  
  19.     //  
  20.   
  21.     // 这里以弹出一个对话框为例子  
  22.     //  
  23.     MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);  
  24.   
  25.     // 以当前时间为文件名  
  26.     //  
  27.     wstring strDumpFileName = _T("C:\\") + GetPresentTime() +_T(".dmp");  
  28.   
  29.     // 创建Dump文件  
  30.     //  
  31.     CreateDumpFile(strDumpFileName.data(), pException);  
  32.   
  33.     return EXCEPTION_EXECUTE_HANDLER;  
  34. }  
  35.   
  36. int _tmain(int argc, _TCHAR* argv[])  
  37. {  
  38.     // 设置处理Unhandled Exception的回调函数  
  39.     //   
  40.     SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashHandler);   
  41.   
  42.     __try  
  43.     {  
  44.         MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);  
  45.   
  46.         // 除零,人为的使程序崩溃  
  47.         //  
  48.         int i = 13;  
  49.         int j = 0;  
  50.         int m = i / j;  
  51.     }  
  52.     __except(CrashHandler(GetExceptionInformation()))  
  53.     {  
  54.         // 在这里添加处理程序崩溃情况的代码  
  55.         //  
  56.   
  57.         // 这里以弹出一个对话框为例子  
  58.         //  
  59.         MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);  
  60.     }  
  61.   
  62.     // 除零,人为的使程序崩溃  
  63.     //  
  64.     int i = 13;  
  65.     int j = 0;  
  66.     int m = i / j;  
  67.   
  68.     MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);  
  69.   
  70.     return 0;  
  71. }  

        编译上面的代码,运行生成的EXE文件,可以看到在弹出上面提到的那一系列的对话框后,程序正常退出,没有崩溃。同时,在C盘下生成了2个Dump文件,文件名指出了发生异常的时刻。

        上面的代码仅仅是为了说明怎样配合使用SEH和SetUnhandledExceptionFilter,所以except后的表达式和 SetUnhandledExceptionFilter中所设置的回调函数都使用了同一个函数CrashHandler。在实际的应用中可以根据不同的 需求而使用不同的函数。这个函数的参数必须是一个指向EXCEPTION_POINTERS的指针,返回值必须是这3个中的一 个:EXCEPTION_CONTINUE_SEARCH,EXCEPTION_CONTINUE_EXECUTION,EXCEPTION_EXECUTE_HANDLER。 这3个值的具体含义可以查阅MSDN或者我的那篇《让程序在崩溃时体面的退出之SEH》。
        实际情况下,是不应该用同一个回调函数的。因为在except表达式中的函数是处理try块中的代码异常的;而用 SetUnhandledExceptionFilter设置的回调函数是用来处理代码中没有被捕捉到的异常的。对于未被捕捉到的异常,这个回调函数是不 知道异常发生的地方的,虽然可以通过异常代码知道异常的类型,但是由于不知道是什么状况引起的异常,所以没法做出相应的异常处理。一般情况下,这个回调函 数是应用程序崩溃前的最后一道防线,这个函数中的代码被执行完后,应用程序就会被终止。所以,大部分的应用程序,在这个函数里都是弹出一个发送错误报告的 对话框,来告诉用户程序发生异常,需要终止,可以把错误报告(一般是包括Dump文件和一些必要的文本信息)发送到指定地方帮助开发者来修改代码缺陷,以 提高软件质量。

        使用上面的方法编写出的应用程序不会崩溃,并且在出现异常的时候会产生Dump文件。程序的使用者会获得非常良好的用户体验。如果再给应用程序添加上Log信息,配合上Dump文件,就可以很轻松的定位程序中的异常,帮助开发者快速的修复代码中的错误。

<think>好的,我现在需要帮助用户解决在UE C++中获取程序崩溃日志和调试信息的问题。首先,我得回忆一下UE相关的错误处理机制和日志系统。根据用户提供的引用,特别是引用[4]提到日志的重要性以及如何在UE5 C++中打印日志,这可能是一个相关的点。用户需要的是在程序崩溃自动捕获崩溃信息,而不仅仅是主动打印日志。 首先,我应该考虑UE内置的崩溃报告功能。记得UE引擎本身有崩溃收集系统,比如Crash Reporter,但用户可能需要自定义处理或者在崩溃记录更多信息。可能需要设置崩溃处理回调函数,使用像_set_purecall_handler这样的函数来捕获特定的错误类型,比如纯虚函数调用错误。 然后,Windows平台有结构化异常处理(SEH),可以使用__try/__except块来捕获异常。但UE可能有自己的封装,所以需要结合SEH和UE的日志系统。例如,在崩溃触发一个回调,将调用堆栈信息记录到日志文件中。 另外,引用[2]提到UE的反射机制,但不确定这和崩溃日志直接相关。不过反射可能用于序列化或其他功能,可能间接影响日志记录的方式。不过重点应该放在错误处理和日志捕获上。 接下来,考虑如何生成崩溃转储文件(dump文件),这在Windows上通常通过MiniDumpWriteDump函数实现。用户可能需要在崩溃自动生成dump文件,以便后续分析。同,结合UE的日志系统,确保崩溃前的日志被正确保存。 还需要注意,UE的日志默认输出到特定的位置,如项目文件夹的Saved/Logs目录下。引用[4]提到查看日志的位置,这可能帮助用户找到自动生成的日志文件。但崩溃可能需要确保日志被及刷新写入,避免丢失。 此外,引擎的崩溃上下文处理,比如使用FCrashGame类,可能在特定情况下触发,但需要查阅更多UE文档。可能还需要在项目设置中启用崩溃报告生成,或者添加自定义的崩溃处理模块。 最后,总结步骤:设置崩溃回调,捕获异常,记录堆栈信息,生成dump文件,配置项目设置,并指导用户如何查看生成的日志和dump文件。同,要确保代码示例正确,比如使用Windows API和UE的日志宏,如UE_LOG。 可能还需要提醒用户编译配置,如在Debug或Development模式下才能获取详细堆栈信息,以及在打包版本中如何处理崩溃日志。此外,引用[3]提到的头文件可能需要包含相关API的头文件,比如Windows.h或HAL/PlatformMisc.h。 需要验证每一步的正确性,比如_set_purecall_handler是否正确应用,以及确保SEH在UE项目中的兼容性。同,确保代码示例符合UE的编程规范,比如使用正确的宏和函数。 总结来说,解决方案应包括异常处理、日志记录、dump文件生成和项目配置,结合UE提供的工具和API,以及平台特定的方法如Windows的SEH和MiniDump函数。</think>在UE C++中获取程序崩溃日志和调试信息可通过以下方法实现: ### 一、核心方法 1. **启用崩溃转储文件生成** - 在项目配置文件中添加: ```ini [CrashReportClient] bEnable=true ``` - 使用Windows API生成minidump: ```cpp #include <Windows.h> #include <DbgHelp.h> LONG WINAPI CrashHandler(EXCEPTION_POINTERS* ExceptionInfo) { MINIDUMP_EXCEPTION_INFORMATION info; info.ThreadId = GetCurrentThreadId(); info.ExceptionPointers = ExceptionInfo; info.ClientPointers = FALSE; HANDLE file = CreateFile(L"crash.dmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal, &info, NULL, NULL); CloseHandle(file); return EXCEPTION_EXECUTE_HANDLER; } ``` 2. **设置全局异常捕获** ```cpp void SetupCrashHandler() { SetUnhandledExceptionFilter(CrashHandler); _set_purecall_handler(PureCallHandler); _set_invalid_parameter_handler(InvalidParamHandler); } ``` ### 二、日志系统集成 1. **使用UE_LOG记录关键信息** ```cpp UE_LOG(LogTemp, Error, TEXT("Crash occurred! Callstack: %s"), *FDebug::StackTraceToString()); ``` 2. **获取调用堆栈** ```cpp FString StackTrace = FPlatformStackWalk::GetStackTrace(); ``` ### 三、引擎配置 1. **项目设置启用详细日志** ```ini [Core.Log] Global=Verbose ``` 2. **崩溃报告模块集成** 在Build.cs中添加: ```csharp PublicDependencyModuleNames.AddRange(new string[] { "CrashDebugHelper" }); ``` ### 四、调试信息配置 1. **生成符号文件** - 在Visual Studio项目属性中启用PDB生成 - 打包勾选"Include Debug Files" ### 五、查看日志位置 - 编辑器模式:`项目目录/Saved/Logs/` - 打包版本:`可执行文件目录/Saved/Logs/` ```cpp // 示例代码:结合异常处理的日志记录 void FMyGameModule::StartupModule() { FWindowsPlatformMisc::SetCrashHandler(CrashHandler); FCoreDelegates::OnHandleSystemError.AddStatic(&HandleCrash); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值