当前位置: 首页 > news >正文

Windows C++程序异常排查实战:从Dump分析到GDI泄漏定位

1. 项目概述:从“程序挂了”到“精准定位”

做C++开发,尤其是Windows桌面应用,最怕的就是程序在测试环境跑得好好的,一到客户现场就“闪退”、“卡死”或者“界面花屏”。面对一个已经崩溃退出的程序,或者一个运行缓慢、内存持续增长的进程,我们手里往往只有一份冷冰冰的dump文件,或者一个“程序无响应”的现场。这时候,如何从这些“死亡现场”的蛛丝马迹中,还原出崩溃或异常发生的完整链条,就是资深开发者与新手之间的一道分水岭。

这个实战经验分享系列,聚焦的就是这个“破案”过程。今天要拆解的几个核心技能点——查看函数调用堆栈、使用Windbg进行动态调试、诊断DLL加载失败、利用API Monitor监控系统调用、分析程序闪退、寻找dump文件以及定位GDI对象泄漏——正是解决这类疑难杂症的“组合工具箱”。它们不是孤立的知识点,而是一套连贯的排查思路:当程序异常时,我们首先需要找到“案发现场”(调用堆栈),然后分析“现场证据”(dump文件),接着追溯“案发过程”(动态调试与API监控),最后揪出“真凶”(如资源泄漏)。掌握这套方法,你就能从被动地“重启试试”,转变为主动地“精准定位,根治问题”。

2. 核心排查工具箱:原理与选型

2.1 调用堆栈:崩溃现场的“时间胶囊”

当程序崩溃或抛出异常时,操作系统或调试器会捕获当前线程的执行状态,其中最关键的信息就是调用堆栈。你可以把它想象成一摞书:最上面那本是当前正在执行的函数,下面压着的是调用它的父函数,再往下是祖父函数,一直回溯到程序的入口点(如mainWinMain)。这摞“书”完整记录了代码执行到崩溃点所走过的路径。

为什么堆栈如此重要?因为它直接回答了“崩溃时程序正在做什么”这个核心问题。一个典型的访问违例(Access Violation)崩溃,堆栈能告诉你是在哪个模块、哪个文件的哪一行代码,试图访问一个非法内存地址。没有堆栈信息,排查就像大海捞针。

获取堆栈的几种方式:

  1. 集成开发环境调试器:如Visual Studio,在调试模式下运行,发生崩溃时会自动中断并显示调用堆栈窗口。这是最直观的方式,但要求你能在开发环境复现问题。
  2. 日志记录:在代码关键位置(如异常捕获块、断言失败处)主动输出堆栈信息到日志文件。这需要集成堆栈回溯库(如Boost.Stacktrace,或Windows APIRtlCaptureStackBackTrace),适用于无法实时附加调试器的场景(如已发布的程序)。
  3. 事后分析(Post-mortem):通过配置系统或程序自身,在崩溃时自动生成dump文件(内存转储文件)。这个文件完整保存了进程崩溃瞬间的内存状态,包括所有线程的堆栈、全局变量、堆内存内容等。这是分析线上崩溃的“黄金标准”。

注意:Release版本的程序通常进行了代码优化(如内联、帧指针省略),这会导致堆栈信息不完整或难以阅读。为了事后调试,建议在发布版本中也保留调试符号(.pdb文件),并使用/Oy-(禁用帧指针省略)等编译选项来生成更友好的堆栈。

2.2 Windbg:Windows调试的“瑞士军刀”

Windbg是微软官方推出的强大调试器,尤其擅长进行事后调试内核调试。对于C++开发者来说,它远不止是一个查看堆栈的工具。

Windbg vs. Visual Studio Debugger:

  • Visual Studio Debugger:强于实时交互式调试。写代码、设断点、单步执行、查看变量,体验流畅,与IDE深度集成。
  • Windbg:强于静态分析和深度挖掘。分析dump文件、查看内核对象、分析内存布局、编写自动化调试脚本。它的命令式操作虽然学习曲线陡峭,但功能无比强大。

为什么在异常排查中必须掌握Windbg?

  1. 分析Dump文件:这是Windbg的核心场景。客户报告崩溃,发来一个.dmp文件,Windbg是打开它的标准工具。
  2. 动态调试无符号程序:即使没有源代码和PDB文件,Windbg也能通过内存地址和导出函数进行分析,这在分析第三方库或系统问题时非常有用。
  3. 强大的内存和句柄分析能力:可以详细列出进程分配的所有堆块、查看内存泄漏、枚举GDI/User对象句柄,直接定位资源泄漏。
  4. 脚本自动化:Windbg支持强大的脚本语言,可以将复杂的分析流程(如遍历所有线程堆栈、统计对象数量)自动化,提高效率。

Windbg Preview:这是微软推出的新一代Windbg,拥有现代化的图形界面,同时保留了强大的命令行引擎。它降低了入门门槛,建议新手从这个版本开始。你可以从Windows应用商店免费下载“WinDbg Preview”。

2.3 API Monitor:系统行为的“监听者”

有时候,问题不在于你的代码逻辑,而在于你的代码与操作系统或其他模块的交互出了问题。比如,一个文件打不开,是路径错误?权限不足?还是杀毒软件拦截?这时,你需要看到程序调用了哪些系统API,以及调用时的参数和返回值。

API Monitor就是这样一款免费的、功能强大的系统调用监控工具。它可以实时监视一个进程对Windows API的调用,包括:

  • 调用了哪个API(如CreateFileW,LoadLibraryExW)。
  • 调用时的参数值(如文件路径、标志位)。
  • API的返回值最后的错误码(通过GetLastError)。

在诊断DLL加载失败时,API Monitor尤其有用。你可以看到程序试图从哪些路径加载DLL,系统返回的错误代码是什么(如ERROR_MOD_NOT_FOUND表示找不到模块,ERROR_INVALID_IMAGE_FORMAT可能是32/64位不匹配),这比单纯看程序日志或错误对话框要清晰得多。

2.4 Dump文件:崩溃瞬间的“全息影像”

Dump文件(内存转储文件)是进程在特定时刻(通常是崩溃或挂起时)的完整或部分内存快照。它包含了分析问题所需的一切:代码、数据、堆栈、寄存器、加载的模块列表等。

Dump文件的类型:

  • 小型转储(Minidump):只包含最基本的信息,如线程堆栈、加载的模块列表、异常记录。文件小,便于传输,是收集线上崩溃信息的首选。通过配置Windows错误报告(WER)或调用MiniDumpWriteDumpAPI生成。
  • 完全转储(Full Dump):包含进程整个用户模式地址空间的内容。文件非常大(可能几个GB),但信息最全,可以查看任何变量的值。通常在初步分析无法定位问题时使用。

如何让程序生成Dump?

  1. 系统级设置:通过注册表组策略配置Windows错误报告,在程序崩溃时自动生成dump。路径通常在C:\ProgramData\Microsoft\Windows\WER\ReportArchive
  2. 代码中集成:通过SetUnhandledExceptionFilter设置顶层异常处理器,在崩溃时调用MiniDumpWriteDump函数生成自定义的dump文件。这是最灵活、最可靠的方式。
  3. 任务管理器:在“详细信息”选项卡中,右键挂起的进程,选择“创建转储文件”。
  4. 使用ProcDump等工具:微软SysInternals套件中的ProcDump,可以监控进程的CPU、内存占用或未处理异常,并在条件触发时自动生成dump。

3. 实战场景拆解:从现象到根因

3.1 场景一:DLL动态库加载失败

“由于找不到xxx.dll,无法继续执行代码。”——这是Windows开发者最常见的噩梦之一。

排查思路与步骤:

  1. 确认现象与错误码:首先,记录完整的错误信息。是启动时弹窗报错,还是运行时调用LoadLibrary失败?获取错误码(通过GetLastError或事件查看器)。
  2. 使用API Monitor进行动态追踪
    • 启动API Monitor,配置要监视的API集合(至少包含LoadLibrary,LoadLibraryEx,GetProcAddress,以及文件相关的CreateFile)。
    • 在API Monitor中启动你的程序,或者附加到已运行的进程。
    • 过滤日志,重点关注对上述API的调用。你会看到程序尝试加载DLL的完整路径序列,以及每次尝试的返回值和错误码。
  3. 分析加载路径:Windows加载DLL有一套严格的搜索顺序:1) 应用程序所在目录;2) 系统目录(System32,SysWOW64);3) Windows目录;4) 当前工作目录;5) PATH环境变量中的目录。API Monitor的日志会清晰展示这个搜索过程。常见问题包括:
    • DLL文件缺失:根本找不到文件。
    • 位元不匹配:32位程序试图加载64位的DLL到System32目录(实际上应该去SysWOW64找),反之亦然。
    • 依赖链断裂:A.dll依赖B.dll,B.dll又依赖C.dll。如果C.dll丢失,加载A.dll也会失败,但错误可能指向A.dll,需要顺藤摸瓜。
  4. 使用Windbg进行静态分析
    • 如果程序已经崩溃并生成了dump,用Windbg打开。
    • 使用lm命令查看已加载和未加载的模块。未加载的模块会显示“Unable to load image”错误,并给出路径和错误码。
    • 使用!dlls命令可以更详细地列出DLL的状态。
  5. 检查清单
    • 依赖查看器:使用Dependency Walker(depends.exe)或Visual Studio自带的dumpbin /dependents命令,检查目标DLL的所有依赖是否都可用。
    • 版本冲突:是否存在多个不同版本的相同DLL(DLL Hell)?程序加载了非预期的版本。
    • 清单文件:检查应用程序清单文件(.manifest)是否正确指定了依赖的Side-by-Side程序集版本。

实操心得:DLL问题经常在开发机器上不出现,一到客户环境就爆发。因此,打包和部署环节的检查至关重要。确保安装包包含了所有必要的VC++可再发行组件包(vcredist),并使用工具检查安装目录下的DLL依赖树是否完整。对于私有DLL,最好放在应用程序同级目录,并确保其依赖项也一并携带。

3.2 场景二:程序无征兆闪退

程序突然消失,没有错误对话框,事件查看器里可能只有一个简单的“应用程序错误”事件。这是最令人头疼的情况。

排查思路与步骤:

  1. 第一步:获取崩溃现场证据(Dump文件)

    • 配置自动生成:这是最重要的前置工作。务必在测试版本和发布版本中集成自动生成MiniDump的机制(通过SetUnhandledExceptionFilter)。这是你事后分析的唯一希望。
    • 从系统收集:如果程序触发了Windows错误报告,去C:\ProgramData\Microsoft\Windows\WER\ReportArchive目录下按时间排序,寻找对应的报告文件夹,里面可能有系统生成的dump。
    • 用户协助:指导用户使用任务管理器生成转储文件,或提供一个小工具(如包装了ProcDump的脚本)让用户在复现问题时运行。
  2. 第二步:使用Windbg进行初步分析

    • 用Windbg打开dump文件,并加载对应的PDB符号文件(符号路径设置:File -> Symbol File Path,添加你的符号服务器路径或本地PDB目录)。
    • 输入!analyze -v命令。这是Windbg最强大的自动化分析命令,它会尝试分析异常原因,给出可能的问题描述、出错的代码位置(如果符号正确),甚至是一些建议。
    • 仔细阅读!analyze -v的输出。它会告诉你异常类型(如ACCESS_VIOLATION)、违规地址、发生异常的线程ID和堆栈。
  3. 第三步:深入分析崩溃堆栈

    • 使用k命令查看当前线程的堆栈。使用~*k可以查看所有线程的堆栈,也许崩溃发生在工作线程,而主线程已经阻塞。
    • 结合源代码,沿着堆栈从上到下阅读。崩溃点(最顶层)的函数不一定有bug,可能是它的调用者传入了非法参数。需要结合堆栈中显示的参数值、以及崩溃地址来分析。
    • 常见崩溃原因分析
      • 空指针/野指针访问:这是ACCESS_VIOLATION最常见的原因。检查堆栈中可疑的指针变量,是否为nullptr或已被释放。
      • 堆栈溢出:通常是无限递归或过大的局部变量数组导致。堆栈会显示很深的、重复的函数调用链。
      • 堆损坏:在崩溃前可能已有征兆。使用!heap命令系列可以检查堆的状态。有时在崩溃点附近使用!address命令查看内存页属性也有帮助。
      • 多线程竞争:如一个线程正在释放内存,另一个线程却在访问它。这种问题在单次dump中可能难以直接发现,需要结合代码逻辑和多个dump分析。
  4. 第四步:检查异常上下文

    • 使用.ecxr命令切换到异常发生时的上下文,然后再次查看寄存器和堆栈,这能确保你看到的是崩溃瞬间最准确的状态。

避坑技巧!analyze -v的输出里,有一行叫FAULTING_IP,它指示了导致异常的汇编指令地址。下面几行FAULTING_SOURCE_CODE可能会显示对应的源代码行。不要100%相信它,特别是当符号文件不匹配或代码经过高度优化时。一定要结合完整的堆栈和你的代码逻辑进行判断。有时它指向的是标准库或系统API内部,这通常意味着是你的代码传递了错误参数导致的。

3.3 场景三:GDI对象泄漏导致的界面异常

GDI(图形设备接口)对象包括画笔(Pen)、画刷(Brush)、字体(Font)、位图(Bitmap)、设备上下文(DC)等。Windows系统对每个进程可用的GDI对象数量有上限(通常默认是10000个)。如果程序持续创建GDI对象而不释放,最终会耗尽配额,导致界面绘制失败、窗口黑屏、白屏,甚至程序崩溃。

GDI泄漏的特征

  • 程序运行时间越长,界面响应越慢,最终卡死。
  • 任务管理器中,进程的“GDI对象”列数值持续增长,只增不减。
  • 出现奇怪的绘制问题,如控件不刷新、文字消失、图片显示不全。

使用Windbg诊断GDI泄漏:

  1. 附加到进程或分析Dump:将Windbg附加到疑似泄漏的进程,或者打开该进程的dump文件。
  2. 查看GDI句柄总数:使用命令!gdi。这会列出进程当前使用的GDI对象统计信息,包括总数和各类型对象的数量。记录下这个数值。
  3. 列出所有GDI句柄的详细信息:使用命令!gdikd.gdihandles(需要内核调试扩展,对用户态dump可能不支持)或更通用的方法:使用!handle命令配合筛选。
    • 首先,!handle 0 0可以列出所有句柄的类型和数量。找到类型为“GDI”的句柄。
    • 更精确的方法是使用!htrace扩展(如果可用)。!htrace -enable启用句柄跟踪,然后让程序运行一段时间再执行操作,最后!htrace -diff可以查看新创建的句柄,这对定位泄漏源非常有帮助。
  4. 分析泄漏的根源:仅仅知道有泄漏还不够,要知道是哪段代码泄漏的。
    • 用户态调试:在怀疑泄漏的代码路径(如创建画笔、位图的函数)前后设置断点,并在断点处执行!gdi命令,观察计数的增长。如果某个函数调用后计数增加,但对应的释放函数调用后计数没有减少,这里就可能存在泄漏。
    • 结合堆栈分析:如果Windbg扩展支持,可以尝试获取创建特定GDI对象的调用堆栈。对于已生成的dump,这通常比较困难。更实用的方法是在代码中集成诊断
  5. 代码级诊断(最有效的方法)
    • 重载new/delete或使用智能指针:对于自定义的包装类,确保资源获取即初始化(RAII)。
    • 在调试版本中打日志:在创建和销毁GDI对象的代码处,输出日志(带线程ID和对象地址),运行一段时间后分析日志,看哪些创建操作没有对应的销毁操作。
    • 使用工具进行实时监控:除了Windbg,还可以使用Process Explorer(SysInternals工具)实时查看进程的GDI句柄计数变化。它的“View -> Show Lower Pane”和“Lower Pane View -> Handles”功能,可以实时刷新并排序句柄,方便观察哪些句柄在持续增加。

实操心得:GDI泄漏常常发生在异常处理路径中。例如,在OnPaint函数里创建了画笔和画刷,在函数返回前进行了释放,但如果在中间某个地方return或抛出了异常,就会跳过释放代码。务必使用RAII对象(如std::unique_ptr配合自定义删除器,或CDCCPen等MFC封装类)来管理GDI资源,利用C++对象生命周期自动管理资源释放。

4. 工具链协同作战:一个完整的排查案例

假设我们收到一个用户报告:我们的图片编辑软件,在连续打开并处理几十张高分辨率图片后,程序界面变白,然后闪退。

我们的排查流程如下:

  1. 复现与监控:在测试环境尝试复现。同时打开Process Explorer,观察目标进程的“GDI Objects”、“USER Objects”和“Private Bytes”(私有内存)计数。发现“GDI Objects”在处理图片过程中持续稳定增长,即使关闭图片窗口也不下降,初步判断为GDI泄漏。
  2. 生成诊断Dump:当GDI对象增长到接近8000个时,使用ProcDump或任务管理器为进程生成一个完全转储(Full Dump),以捕获泄漏发生时的完整内存状态。
  3. Windbg静态分析
    • 用Windbg打开dump文件,加载符号。
    • 执行!gdi,确认GDI对象总数异常高,比如显示Total: 8567
    • 我们需要知道这些对象是什么。执行!handle 0 0 Gdi来尝试筛选。但更有效的方法是,我们知道软件使用了位图(Bitmap),怀疑是位图泄漏。
    • 在Windbg中,可以使用!poolused 2(2代表分页池,GDI对象常在此)并查找与位图相关的标签(Tag),但标签信息需要内核符号,对用户态dump较难。
    • 换个思路:在代码中,我们使用CreateDIBSectionLoadImage来创建位图。我们可以在Windbg中搜索内存中可能与位图相关的数据结构。一个更直接的方法是结合代码分析。
  4. API Monitor动态追踪
    • 重新启动程序,并附加API Monitor。
    • 在API Monitor中,启用对Gdi32.dllUser32.dll中关键API的监控,特别是CreateBitmap,CreateDIBSection,CreateCompatibleBitmap,DeleteObject
    • 执行“打开-处理-关闭”一张图片的操作。
    • 分析日志:过滤出对CreateCompatibleBitmapDeleteObject的调用。你会发现,每次打开图片,都有成对的创建/删除调用。但是,如果存在异常路径,可能会缺少对应的DeleteObject
    • 关键发现:日志显示,在图片解码失败时,程序提前返回,但之前创建的临时内存位图(CreateCompatibleBitmap)没有被删除。
  5. 代码定位与修复
    • 根据API Monitor日志中缺失DeleteObject的调用堆栈(API Monitor可以显示调用堆栈),定位到源代码中图片解码失败的错误处理分支。
    • 检查该分支代码,发现确实在if (decodeFailed) { return false; }之前,没有释放之前创建的GDI位图对象。
    • 修复方案:使用RAII包装类(例如std::unique_ptr<HBITMAP, decltype(&::DeleteObject)>)来管理位图句柄,确保在任何退出路径下资源都能被正确释放。或者,在错误处理分支中显式添加删除逻辑。
  6. 验证修复:修复后,重复测试场景,使用Process Explorer监控,确认GDI对象计数在操作后能回落到基线水平,不再累积。问题解决。

这个案例展示了如何将多种工具组合使用:用Process Explorer做宏观监控和现象确认,用Windbg对崩溃现场做深度检查,用API Monitor对运行时行为进行精细追踪,最终结合源代码分析定位到根本原因。每一种工具都不是万能的,但将它们串联起来,就能形成强大的问题定位能力。

5. 进阶技巧与避坑指南

5.1 Windbg常用命令速查与原理

  • !analyze -v首要命令。自动分析异常,给出初步结论。理解其输出的每个部分(BUGCHECK_STR, EXCEPTION_CODE, FAULTING_IP, STACK_TEXT等)是基本功。
  • k/kb/kp:显示当前线程的调用堆栈。kb会显示前三个参数,kp会详细显示所有参数(需要完整符号)。
  • ~*k:显示所有线程的堆栈。对于多线程程序,必须查看所有线程的状态,主线程可能正在等待一个已经崩溃的工作线程。
  • .ecxr:切换到异常记录上下文。执行!analyze -v后,或当你想查看异常发生时的精确寄存器状态时使用。
  • !heap:显示进程堆的信息。子命令!heap -s看堆段摘要,!heap -p -a <address>可以查看指定地址所在的堆块信息,用于诊断堆损坏。
  • !address <addr>:查看指定内存地址的详细信息(分配类型、大小、保护属性等)。对于访问违例,查看违规地址的属性(是否已提交、可读写)至关重要。
  • lm:列出已加载的模块。配合lm v可以看详细信息。lm m <module_name>可以查找特定模块。
  • !peb:显示进程环境块信息,里面包含了进程的加载模块链表、环境变量、命令行参数等,是了解进程全局状态的好地方。
  • !teb:显示当前线程环境块信息,包含线程的堆栈范围、线程局部存储等信息。

避坑技巧:Windbg命令的输出可能非常冗长。善用日志重定向(File -> Log Window)将输出保存到文件,然后用文本编辑器搜索分析。对于复杂分析,可以编写Windbg脚本(.js或Windbg命令脚本)自动化执行一系列命令。

5.2 符号文件(PDB)配置最佳实践

没有正确的符号文件,Windbg分析dump文件就像看天书,堆栈全是无法解析的地址。

  1. 生成和保存PDB:确保你的构建服务器在编译发布版本时也生成PDB文件。PDB文件必须与对应的EXE/DLL严格匹配(一次构建产生一对)。
  2. 建立符号服务器:这是团队协作的基石。使用微软的SymStore工具或CI/CD流水线,将每次正式构建产生的PDB文件存储到中央符号服务器(可以是一个网络共享文件夹)。
  3. 配置Windbg符号路径:在Windbg中,将符号路径设置为:SRV*C:\SymbolCache*https://msdl.microsoft.com/download/symbols;SRV*C:\MySymbolCache*\\server\share\MyProductSymbols
    • SRV*C:\SymbolCache*https://...:指向微软公有符号服务器,用于下载系统DLL的符号。
    • SRV*C:\MySymbolCache*\\server\share...:指向你公司内部的符号服务器。
    • Windbg会按顺序查找,并将下载的符号缓存到本地目录(C:\SymbolCache),避免重复下载。
  4. 调试时加载符号:打开dump后,使用.reload /f命令强制重新加载所有符号。使用lm命令查看模块状态,如果看到“Symbols loaded”或“Deferred”,通常表示符号正确。如果看到“Export symbols”,则表示只有导出表符号,没有源代码行号信息。

5.3 编写健壮的崩溃收集与报告机制

让程序在用户端优雅地崩溃并上报信息,是提升软件质量的关键。

  1. 设置顶层异常过滤器:在mainWinMain函数开始处,调用SetUnhandledExceptionFilter注册你自己的异常处理函数。这个函数会在程序发生未处理异常(大部分崩溃)时被调用。
  2. 生成MiniDump:在异常处理函数中,调用MiniDumpWriteDump函数。建议生成MiniDumpWithFullMemoryMiniDumpWithDataSegs等包含较多信息的类型,以便后续分析。
  3. 收集上下文信息:除了dump,还应该收集:
    • 异常代码和地址。
    • 发生异常的模块名称和版本。
    • 操作系统版本、语言、时区。
    • 程序本身的版本、配置信息。
    • 用户操作日志(如果涉及)。
  4. 安全地上报:将dump文件和上下文信息打包,尝试通过HTTP/HTTPS安全地发送到你的服务器。上报过程本身要简单、快速,最好在独立线程中进行,避免二次崩溃。要处理好用户隐私,明确告知用户收集了哪些数据。
  5. 自动化分析:服务器收到dump后,可以尝试用Windbg的命令行版本(cdbwindbg -z)配合脚本进行自动化初步分析,提取关键信息(如异常类型、崩溃堆栈顶部),并归类到问题跟踪系统(如JIRA, Bugzilla)。

一个简单的示例代码框架:

#include <Windows.h> #include <DbgHelp.h> #pragma comment(lib, "DbgHelp.lib") LONG WINAPI MyUnhandledExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo) { // 生成dump文件名,包含时间戳和进程ID SYSTEMTIME st; GetLocalTime(&st); wchar_t dumpPath[MAX_PATH]; swprintf_s(dumpPath, L"C:\\CrashDumps\\MyApp_%04d%02d%02d_%02d%02d%02d_%d.dmp", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, GetCurrentProcessId()); // 创建目录 CreateDirectory(L"C:\\CrashDumps", NULL); HANDLE hFile = CreateFile(dumpPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION mei; mei.ThreadId = GetCurrentThreadId(); mei.ExceptionPointers = pExceptionInfo; mei.ClientPointers = FALSE; // 生成包含较多信息的MiniDump MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo), &mei, NULL, NULL); CloseHandle(hFile); } // 这里还可以记录日志、尝试上报等 // ... // 返回EXCEPTION_EXECUTE_HANDLER会让进程终止,并弹出系统错误对话框 // 返回EXCEPTION_CONTINUE_SEARCH会让系统默认处理(也通常是终止) return EXCEPTION_EXECUTE_HANDLER; } int main() { SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); // ... 你的程序主逻辑 return 0; }

这套机制建立起来后,你就能从被动的“用户说崩溃了”转变为主动的“我们收到了一个来自XX版本在XX操作下的崩溃dump”,极大地提升了问题定位和修复的效率。

http://www.jsqmd.com/news/1358248/

相关文章:

  • 2026年浙江国内二手不锈钢离心机有哪些?这份甄选指南帮你择优而选 - geo交流
  • UE4 Niagara 2D流体模拟实战:Advect Grid 2D Collection核心原理与应用
  • 2026年苏州优质全铝餐边柜品牌推荐:行业标杆品牌靠谱挑选指南
  • Transformer架构核心解析:从自注意力到工程实践
  • UE5虚拟阴影贴图(VSM)小物体阴影缺失:原理分析与四步修复方案
  • 机器人走着走着就失控
  • image 2 盘点各种玩法!【附提示词】
  • 洗地机批发怎么选?这3招教你找到靠谱厂家
  • Spring Boot中设计模式的实践与优化
  • 厦门企业股权转让评估机构靠谱推荐,价格透明避坑指南 - 工业设备
  • Win10/Win11系统下VC++ 6.0稳定安装与配置全攻略
  • 掌控板热敏传感器编程入门:从模拟信号到温度监测
  • 番茄小说Python爬虫实战:抓取免费小说标签与读者画像
  • 伊犁全屋漏水别瞎修!9大渗水场景一次讲透,省心修缮不踩坑 - 宅安选房屋修缮
  • 高延迟游戏PvP制胜策略:从信息差攻击到逆境工程思维
  • 2026年北京知名的二手乳品生产线转让如何优选?这份甄选指南助你择优 - geo交流
  • UV喷码机厂家口碑推荐,价格透明零套路不踩坑 - 工业品牌热点
  • 二阶锥优化在电力系统无功多目标优化中的应用
  • 技术翻译工作流设计:悟空型敏捷与超人型系统化策略融合实践
  • 2026届必备的六大降重复率网站实际效果
  • 【寄电动车能带电池吗?2026年电动车托运全攻略+避坑指南】 - 快递物流资讯
  • 排队论实战:从Gen Con 2026现场74000名观众看大型活动容量规划
  • JMeter断言原理与实战:接口测试质量保障
  • 数位板压感调节全攻略:从原理到实战,解锁专业绘画手感
  • 2026年上门老茅台酒回收公司甄选指南:三步对比出价,帮你避开回收陷阱 - geo交流
  • 动态规划与状态压缩在网格收集问题中的应用
  • 如何在浏览器中零配置实现SQL数据可视化:sqliteviz快速入门指南
  • 郑州移动网站建设专业指南:从零基础到流量变现的实战策略
  • Claude Sonnet 写完整个项目后,我才发现 AI Agent 最擅长的是挖坑——Agent 编程的 7 个止损开关
  • 2026年毕业论文降AI工具推荐:5款免费工具亲测,效果最好的竟然最便宜