首页
社区
课程
招聘
[原创]调试实战 | 使用 GFlags 与 WinDbg 定位 VS2022 “重复释放” 引发的崩溃
发表于: 2025-12-13 14:37 790

[原创]调试实战 | 使用 GFlags 与 WinDbg 定位 VS2022 “重复释放” 引发的崩溃

2025-12-13 14:37
790

摘要

本文记录了一次独特的调试经历:作为开发利器的 Visual Studio 2022,其在切换调用栈时频繁崩溃。面对这一问题,利用 procdump 自动捕获崩溃转储文件,并通过 WinDbg 初步排查将问题指向堆内存的异常操作,可能是堆损坏或重复释放。为了精准定位,我启用 gflags 工具开启页堆检测,最终成功捕获到首次释放操作的完整调用栈,明确问题根源在于VSDebug!treegrid::CTreeGridItemContainerGenerator::Refresh过程中的内存重复释放。虽然因缺少源码无法直接修复,但通过环境隔离(关闭特定程序)避免了问题复现。此次实战再次证明了 procdumpgflags 等工具在诊断复杂内存问题中的巨大价值,也提醒我们即使面对没有源码的“黑盒”组件,系统化的调试方法依然能指引我们找到问题的本质。

缘起

最近,用 vs2022 在调试的时候,切换调用栈,会有很大概率崩溃。一次两次就忍了,不停的崩溃就有点说不过去了。 话不多说,先放张动图看看 vs2022 是怎么崩溃的。

vs2022崩溃

好在我已经设置了 procdump 为事后调试器,每当有进程崩溃的时候,都会在 d:\dumps\ 目录下保存一份转储文件。下图是最近保存的一些转储文件(已经清理过几次了)。

recent-dump-files

初步分析

老规矩,使用 windbg 打开对应的转储文件,先无脑 !analyze -v 一波,没看到有用的信息。执行 k 命令查看调用栈,如下图:

delete-exception

看到熟悉的 delete 基本就猜到是堆出问题了,或者是堆破坏或者是重复释放。如果能找到 delete 的地址,然后使用地址相关命令(比如, !address 或者 !heap -x addr 命令)应该是可以看到一些信息的。那么该如何找到这个地址呢?

查找关键地址

64 位程序一般是 __stdcall,一般第一个参数是通过 rcx 传递的,但是寄存器的值随着调用其它函数是会改变的,除非保存到栈上过。为了查看传递给 delete 的参数,找到调用它的栈帧。找到栈帧 10 (VSDebug!operator delete+0x9)对应的返回地址(00007fff98f13082),使用 ub 查看对应的反汇编。查看是否有保存 rcx 的操作,没有的话,继续向调用方向找(当然也可以向被调用方向查找是否有保存 rcx 的操作)。直到找到栈帧 12 的返回值地址,

12 0000002899dad690 00007fff99103111 VSDebug!CClassFactory<CRefCount>::Release+0x27

使用 ub 00007fff99103111 L28(为什么 L28,因为好截图)

find-the-addres-being-deleted

从图中可知,rcx 来自 rbx,而 rbx 的值被保存到当前栈帧(栈帧 13) rsp+0x40 的位置上过。

执行 dq 0000002899dad6c0 + 0x40 L2 然后执行 !address 0000014185ce4360,然后执行 !heap -x 0x14185ce4360,可以发现这个地址确实是 free 的。如下图:

verify-the-adress-has-been-deleted

虽然这里的值不是 100% 靠谱,但是也能在一定程度上证实我们的猜测。其实,解决堆相关问题,可以使用神器 gflags,可以在尽可能早的时候把问题暴露出来。

设置 gflags

打开 gflags.exe,切换到 Image File 页,在 Image:(TAB to refresh) 后面的编辑框内输入 vs 的进程名 devenv.exe,然后按 TAB 键。可以无脑勾选跟堆相关的所有选项。每一项的具体意义可以询问 AI,给出的解释比较靠谱。

set-gflags-for-devenv

当然,也可以通过命令行执行,最终都是操作注册表(下表摘自微软官方文档):

设置类型注册表位置
系统级设置(注册表)HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\GlobalFlag
特定程序的设置(“映像文件”)适用于计算机的所有用户。HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ImageFileName\GlobalFlag
特定程序的无提示退出设置(“无提示进程退出”)适用于计算机的所有用户。HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\ImageFileName
计算机的所有用户的图像文件的页堆选项HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ImageFileName\PageHeapFlags
用户模式堆栈跟踪数据库大小 (tracedbHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ImageFileName\StackTraceDatabaseSizeInMb
为图像文件创建用户模式堆栈跟踪数据库(ust、0x1000)Windows 将映像文件名添加到 USTEnabled 注册表项的值(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\USTEnabled)。
在可能的情况下大型页加载映像HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ImageFileName\UseLargePages。
特殊池(内核特殊池标记)HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PoolTag
验证开始/验证结束HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PoolTagOverruns。 “ 验证开始 ”选项将值设置为 0。 “ 验证结束 ”选项将值设置为 1。
映像文件的调试器HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ImageFileName\调试器
对象引用跟踪HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel\ObTraceProcessName ObTracePermanent 和 ObTracePoolTags

配置好后,再次重现问题,打开转储文件,再次进行分析。

再次分析

执行 k 命令,可以发现在 GetParent 函数内部触发了异常。如下图:

callstack-after-turn-on-gflags

执行 .ecxr 查看异常发生时的上下文,如下图:

ecxr-after-turn-on-gflags

第一眼就感觉 rax 的值好特殊啊。看看它的来源,很幸运,在崩溃代码附近(00007fff984e309a)执行 ub 和 u 操作可以找到完整信息,如下图:

virtual-function-call-when-crash

红色高亮部分是非常典型的虚函数调用代码。rax 指向虚表,rbx 是对象地址。非常幸运的是在红框上方,把 rbx 保存到了栈 rsp+40h 的地方。其实,没必要,因为当前上下文是异常发生时的上下文(执行了 .ecxr 命令),直接可以获取 rbx 的值。通过查看栈上的值,也佐证了这个观点。

verify-rbx-by-rsp

拿到对象地址后,执行 !address 000001a053fd31f0 和 !heap -x 000001a053fd31f0

view-heap-address-info

从图中没看出特别明显的错误(尤其注意 Flags 的值)。我们已经使用 gflags 开启页堆,并且开启了 ust,可以通过 !heap -p -a 0x1a053fd31f0 命令查看此地址相关的调用栈,如下图:

heap-p-a-address

从上图可知,在 VSDebug!treegrid::CTreeGridItemContainerGenerator::Refresh 的时候已经触发了 delete 操作,至此,已经可以确认这是个重复释放的问题了,而且第一次释放时的调用栈也很清楚。

虽然问题已经很明确了,但是我堆 f0f0f0f0f0f0f0f0 这个填充模式充满了好奇。

填充模式

我在查看 windbg 帮助文档关于 !heap 部分时,意外发现了它的意义。原来是开启轻型页堆时,内存被释放后,会用此模式填充。

fill-pattern-explanation-from-windbg-help-document

而且,我还发现了其它几个有意思的填充值,赶紧实战验证下,通过上面的 !heap -x 000001a053fd31f0 命令已经得到了对应的 Heap Entry 的地址是 000001a053fd31a0。使用 dd 000001a053fd31a0 L80 命令看一下这段内存数据的值。

view-memory-patttern

解决问题

虽然查到问题了,但是没有代码,也没法解决。但是在整个折腾的过程中发现只有开着某个特定程序的时候,vs 才会崩溃,关闭这个特定程序后 vs 就不再崩溃了。应该是那个程序使用了 UIA 相关接口,做了一些事情,导致 vs 崩溃。

就不再继续折腾了,有时候不是所有问题都要有一个明确的答案,这也算是解决问题的一种方式吧。

亲自动手

我已经把对应的转储文件上传到百度云盘了,感兴趣的小伙伴可以下载,亲自实战一番。

链接: 761K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4y4g2S2S2i4K6u0V1M7p5y4W2k6i4A6W2d9p5&6%4e0W2k6%4d9X3k6s2P5Y4S2m8 提取码: puv8

参考资料

bd9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0V1K9r3q4J5k6s2N6S2M7X3g2Q4x3V1k6V1M7X3W2$3k6i4u0K6i4K6u0r3k6r3g2T1N6h3N6Y4k6i4u0Q4x3V1k6Y4k6X3I4S2k6%4y4Q4x3X3c8V1k6i4c8S2K9h3I4K6

总结

  • procdump 是收集转储文件的神兵利器,一定要放到自己的武器库里
  • gflags 是解决内存破坏问题的神器,一定要放到自己的武器库里
  • gflags 最终结果是设置注册表,必要时可以手动设置
  • x64 位程序默认调用约定是 __stdcall,第一个参数一般会通过 rcx 传递



[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 2
支持
分享
最新回复 (2)
雪    币: 2839
活跃值: (12132)
能力值: (RANK:385 )
在线值:
发帖
回帖
粉丝
2
图没啦.就我看不到图吗?
2025-12-16 11:34
1
雪    币: 8526
活跃值: (9206)
能力值: ( LV12,RANK:360 )
在线值:
发帖
回帖
粉丝
3
TkBinary 图没啦.就我看不到图吗?[em_002]
我以为会自动上传图片到看雪服务器,刚看了一下,还是用我的 http 的图床链接。
可以在 chrome 中通过 chrome://flags (在 edge 中通过 edge://flags)启用 Insecure origins treated as secure,并且把图床地址 353K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4u0W2M7$3!0#2M7X3y4W2M7#2)9J5k6h3u0A6j5h3&6U0K9r3g2F1k6$3&6S2L8W2)9J5k6i4c8W2j5$3S2Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4@1f1#2i4K6S2m8i4@1p5H3i4@1f1#2i4K6R3#2i4@1p5#2i4@1f1#2i4K6R3^5i4@1t1H3i4@1f1@1i4@1u0r3i4@1p5I4i4@1f1@1i4@1u0n7i4@1u0n7i4@1f1#2i4K6R3^5i4K6V1%4i4@1f1^5i4@1p5I4i4@1p5^5i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1$3i4K6V1$3i4@1t1&6i4@1f1#2i4@1u0o6i4K6S2r3i4@1f1$3i4K6W2r3i4@1p5#2i4@1f1%4i4K6W2o6i4K6S2n7i4@1f1#2i4K6W2n7i4@1u0q4i4@1f1%4i4K6R3&6i4K6R3%4i4@1f1K6i4K6R3H3i4K6R3J5
2025-12-17 21:53
0
游客
登录 | 注册 方可回帖
返回