BianChengNan's Blog

Coding is hard, you can make it easy!


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

置顶声明

发表于 2029-03-01 | 更新于: 2025-12-13 | 分类于 原
字数统计: | 阅读时长 ≈ 分钟

实在抱歉,因为图片使用的是 http 链接,在 chrome 或者 edge 浏览器中打开本博客的时候,看不到文章中的图片。

可以在 chrome 中通过 chrome://flags (在 edge 中通过 edge://flags)启用 Insecure origins treated as secure,

并且把图床地址 http://resources.bianchengnan.tech 加入到信任列表的方式查看图片。(非常感谢群友 张帆 的提示)

整个操作如下图:

enable-show-image-in-chrome

如果还不能查看相关图片,请联系我,或者到我的公众号里查看。

我的个人微信号是 BianChengNan,公众号是 编程难。

内存都去哪了?探究 VirtualAlloc 分配背后被“浪费”的 60KB

发表于 2025-12-06 | 更新于: 2025-12-13 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

摘要

本文记录了一次因 VirtualAlloc 分配失败引发的 OOM 问题排查过程。通过编写测试程序模拟内存分配,发现 32 位进程在未开启 Large Address Aware 时,用户空间仅 2GB 可用,且 VirtualAlloc 实际分配粒度均为 64KB——若申请 4KB,剩余 60KB 将变为不可用空间,导致地址空间碎片化与大量浪费。借助 VMMap 碎片视图与 Windbg 分析,直观展示了分配粒度对内存布局的影响,并验证了按 64KB 对齐分配可避免该问题。

阅读全文 »

排错实战 | 当编译器"吃掉"函数声明:一次由宏冲突引发的离奇编译错误

发表于 2025-12-06 | 更新于: 2025-12-13 | 分类于 排错
字数统计: | 阅读时长 ≈ 分钟

摘要

在编译 .NET Runtime 源码研究 GC 机制时,我遇到了一个离奇的编译错误:函数 __asan_handle_no_return 的声明被编译器报错为”类函数宏的调用”。通过将源文件预处理输出到中间文件,我发现这个函数声明竟然变成了 void ;——它被”吃掉”了!追踪发现,在 utils.h 中定义了一个同名宏,当 __SANITIZE_ADDRESS__ 宏未定义时,该宏被展开为空,导致函数声明被意外删除。这个案例再次证明:宏命名冲突是编译错误的常见陷阱,而预处理输出文件是诊断这类问题的利器。同时,FileLocator 这类文件搜索工具在源码分析中不可或缺。

阅读全文 »

深入.NET Runtime:一次 OOM 异常的分析与源码追踪之旅

发表于 2025-11-29 | 更新于: 2025-12-13 | 分类于 NET
字数统计: | 阅读时长 ≈ 分钟

摘要

本文记录了一次对 .NET 应用程序发生的“内存不足”(OOM)异常进行的深度源码级调查。问题始于一个看似矛盾的现象:诊断工具显示有足够大的空闲内存块(约 50MB),但垃圾回收(GC)过程却在尝试预留较小内存段(约 16MB)时失败。

为了探究根源,笔者逆向追踪了诊断工具(如SOS)输出的数据链路:从高层诊断命令(AnalyzeOOMCommand)入手,逐步深入Microsoft.Diagnostics.Runtime (CLRMD)库、托管辅助类、Dac 接口,最终直达 .NET Runtime(CoreCLR) 底层的 GC 相关 Native 代码(如 ClrDataAccess、gc_heap)。

虽然最终并没能定位问题的真实原因,但是理清了 GC 在 virtual_alloc过程中因内存限制检查、地址空间布局考量等因素导致预留失败的具体逻辑,并对 .NET 源码有了一定的认识,还是非常值得记录分享的。

阅读全文 »

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

发表于 2025-11-29 | 更新于: 2025-12-13 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

摘要

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

缘起

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

vs2022崩溃

阅读全文 »

调试实战 | 一个隐蔽的崩溃:当 this 指针在构造函数中“杀死”自己

发表于 2025-11-22 | 更新于: 2025-12-13 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

摘要

在最近的项目开发中,我遇到了一个由智能指针误用导致的程序崩溃问题。问题的根源在于 SheetDataHandler 类的构造函数中,将 this 指针传递给了一个接收 SheetDataHandlerPtr(智能指针类型)参数的静态函数 HandleMissingColumn。这个看似简单的操作,却导致了对象在构造函数执行期间被意外释放,最终引发空指针访问异常。

通过深入调试和反汇编分析,我发现当 this 指针被隐式转换为智能指针时,引用计数会从 0 增加到 1,而在函数调用结束后,智能指针对象析构时引用计数又减回 0,从而触发了对象的 delete 操作。这导致构造函数尚未执行完毕,对象就已经被销毁,后续对成员变量的访问变成了访问已释放内存的非法操作。

阅读全文 »

调试实战 | 永远不要忽略编译警告:记一次由重复switch语句导致的诡异崩溃

发表于 2025-11-22 | 更新于: 2025-12-13 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

摘要

本文记录并剖析了一次由看似低级的代码错误引发的、令人意想不到的程序崩溃。问题的根源在于 GetErrorStr 函数中一个容易被忽略的重复 switch语句。这个错误导致函数返回的 std::wstring 对象未被正确初始化,最终在构造 CResult 对象时引发了空指针访问异常 。

本文通过深入分析崩溃调用栈和反汇编代码,清晰地总结了从函数异常返回(未初始化字符串)到调用端使用无效数据(触发崩溃)的完整过程。希望这个案例能提醒各位,编译警告是发现潜在风险的第一道防线,而基础的汇编知识则是深入调试的利器。文末有可复现问题的代码,欢迎动手实践。

阅读全文 »

基础知识 | 函数基础 5 —— 实战修复虚函数导致的编译错误

发表于 2024-12-01 | 更新于: 2025-12-13 | 分类于 基础知识
字数统计: | 阅读时长 ≈ 分钟

缘起

前一阵子,同事遇到了一个奇怪的编译问题,大概情况如下:

classA 是模块 A 中的一个类, classA 没有定义构造函数,其它函数都是导出的。B 模块依赖了 A 模块,并且会调用 classA 的接口。当在 B 模块中添加了实例化 classA 对象的代码的时候,报链接错误,提示找不到 classA 类的某个虚函数。

我帮忙看过之后发现又是虚函数相关的编译问题(正好最近在总结虚函数相关的问题),这是送上门的素材啊!

阅读全文 »

基础知识 | 函数基础 4 —— 又崩溃了,原来是虚函数声明顺序不一致捣的鬼

发表于 2024-11-09 | 更新于: 2025-12-13 | 分类于 基础知识
字数统计: | 阅读时长 ≈ 分钟

缘起

前一阵子,同事遇到了一个崩溃问题,解决后发现这个崩溃是由于在公共类中加了一个虚函数接口,但是并没有编译相关模块导致的。这种崩溃问题是老朋友了。在此之前,我已经写了几篇关于虚函数的总结,感兴趣的小伙伴儿可以查看这几篇文章:

《基础知识 | 有趣的动态转换》

《基础知识 | C++ 虚函数简介》

《基础知识 | c++ 有趣的动态转换之 delete 崩溃探究兼谈基类虚析构的重要性》

《基础知识 | 函数基础 1 —— 基本概念 & 如何调用外部模块的函数》

《基础知识 | 函数基础 2 —— 如何不依赖外部模块却能调用它的函数?》

《基础知识 | 函数基础 3 —— 跨模块调用未导出虚函数的各种姿势 》

本文主要关注以下两个问题,如果你已经有了很明确的答案,可以跳过本文:

  • 如果在编译 A 模块的时候,Test 类的虚函数声明的顺序是 Test1, Test2, Test3,但是在 B 模块编译的时候,Test 类头文件中虚函数顺序变成了 Test2, Test1, Test3。在 B 模块中调用 test->Test1(),调用的是哪个函数呢?
  • 假设 A 模块代码不变,但是在编译 B 模块的时候,Test 类的头文件中又多了一个虚函数 Test4(),在 B 模块中调用 test->Test4(),代码可以正常编译吗?会有链接问题吗?如果可以正常编译链接,运行的时候会有问题吗?
阅读全文 »

基础知识 | 函数基础 3 —— 跨模块调用未导出虚函数的各种姿势

发表于 2024-09-14 | 更新于: 2025-12-13 | 分类于 基础知识
字数统计: | 阅读时长 ≈ 分钟

缘起

在上篇文章 《基础知识 | 函数基础 2 —— 如何不依赖外部模块却能调用它的函数?》中,我们明白了一个事实 —— 可以在不依赖外部模块的情况下通过类对象指针调用其虚函数。

但是遗留了几个问题,如下:

  • 问题 1:Interface1 类中没有声明构造函数,编译器生成的构造函数保存在哪里?GetInterface 模块还是 Interface 模块?

  • 问题 2:Interface1 的虚表保存在哪里?GetInterface 模块还是 Interface 模块?

  • 问题 3:如果去掉 Interface1 中虚函数的导出符号,上述代码能编译通过吗?

  • 问题 4:如果在 Interface1 中声明了未导出的构造函数,上述代码能编译通过吗?

  • 问题 5:如果 InterfaceBase::Test1() 不是纯虚函数,上述代码能编译通过吗?

  • 问题 6:如果 InterfaceBase 的析构函数不是虚函数,上述代码能编译通过吗?

本文力求把这几个问题弄清楚。如果您对以上问题已经有了答案,可以跳过本文。

阅读全文 »
12…14
BianChengNan

BianChengNan

140 日志
34 分类
227 标签
RSS
GitHub 知乎 博客园
© 2019 — 2025 BianChengNan | 全博客共 字
0%