BianChengNan's Blog

Coding is hard, you can make it easy!


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

调试实战 | 调试另外一个由于全局变量初始化顺序导致的 dll 加载失败问题(中)

发表于 2024-05-12 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

在上篇文章《调试实战 | 调试另外一个由于全局变量初始化顺序导致的 dll 加载失败问题(上)》中,解决了由于全局变量初始化顺序不对导致的崩溃问题。但是代码里还有一处非常隐蔽的 bug,今天继续介绍一下这个问题及对应的解决方法。

阅读全文 »

调试实战 | 调试另外一个由于全局变量初始化顺序导致的 dll 加载失败问题(上)

发表于 2024-03-23 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

最近又遇到了一个程序功能不正常的问题,深入调查后发现与全局变量初始化顺序有非常大的关系,只不过这次更加隐蔽。

之前总结了两篇与全局变量初始化顺序有关的文章,感兴趣的小伙伴儿可以参考《调试实战 | dll 加载失败之全局变量初始化篇》 和 《调试实战 | 全局变量初始化顺序探究》。

阅读全文 »

2024 开工喽

发表于 2024-02-20 | 更新于: 2026-02-17 | 分类于 年度总结
字数统计: | 阅读时长 ≈ 分钟

回首 2023

回顾整个 2023 ,相比 2022 加班少了,工作没那么拼命了。

由于各种原因,年初立的 flag 好几个都没实现。

  • 公众号基本上处于鸽的状态
  • 也没分享技术视频
  • 语言倒是接触了一下 rust,但远没有达到能实战的地步
  • 嘴没管住,腿倒是迈开了

展望 2024

我对 2024 这个数字感到非常亲切,2024 = 1000 + 1024,两个一千

  • 今年的首要任务依旧是锻炼身体

    本来计划 2024 年跑步作为日常锻炼的方式,结果 2023 年最后一次从公司跑回家后膝盖疼,2024 只能偶尔跑跑了

  • 继续遛狗
    2023 花费了很大一部分时间在遛狗上,2024 继续努力。如果不是狗子的陪伴,估计我早抑郁了,感谢,感恩。
  • 坚持练习英语口语

    希望这次不要因为任何原因中断

  • 继续坚持分享技术文章

    遇到值得总结的问题,及时总结分享

  • 读一些非技术书籍

    之前看的书以技术书籍为主,2024 年争取多看些非技术的书籍

  • 做一些改变

    尽量熟悉 AI 相关的人和事

调试实战 | 记一次有教益的递归栈查看(续)

发表于 2024-01-06 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

在上篇文章中介绍了在 windbg 中如何查看非常深的调用栈 —— 使用 kN 命令指定栈帧数。kN 虽好,但最多只能查看 0xffff 个栈帧。如果栈帧数量比 0xffff 还多,该如何查看呢?本文将介绍几种查看方法。

阅读全文 »

调试实战 | 记一次有教益的递归栈查看

发表于 2024-01-06 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

最近,遇到了一个由于递归导致的卡死问题。这个问题非常有意思,值得总结。

你知道什么情况下无限递归会卡死,而不崩溃吗?你知道递归层数过多时,如何找到导致递归调用的函数吗?你知道如何快速找到关键线程吗?你知道如何附加到一个正在被调试的进程吗?你知道如何在 windbg 中显示指定数量的栈帧吗?

带着这些疑问,一起来看看这个非常有意思的问题吧。

说明: 文章末尾有这些问题的答案,可以直接跳到末尾查看。

阅读全文 »

调试实战 | 从转储文件找出抛出的异常 —— 实战

发表于 2023-12-31 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

我在上一篇文章中介绍了定位抛出异常的理论知识,本文会通过几个实例介绍各种情况下的定位方法。有调试符号如何定位?没有调试符号如何定位?32 位程序如何定位?64 位程序又该如何定位?

其实,32 位程序和 64 位程序定位过程大同小异,只不过在解析过程中需要注意,很多关键字段在 64 位程序中是偏移,需要加上模块基址得到虚拟地址后才能使用,而在 32 位程序中对应的字段就是虚拟地址,可以直接使用。

没有调试符号的时候定位异常类型会比较困难,需要根据上一篇文章中总结的步骤一步步的找到异常类型。有调试符号的情况会比较容易,有很多简便的查看方法。

一起来实战吧!

阅读全文 »

调试实战 | 从转储文件找出抛出的异常 —— 理论

发表于 2023-12-30 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

最近在分析转储文件时,遇到了一个由 throw 抛出的异常。尽管在 windbg 中使用 !analyze -v 迅速知道了异常码是 0xe06d7363(对应的 ASCII 码是 .msc),但是根据异常码并不能确定具体抛出来的是哪种异常。针对这种情况,确定具体的异常类型才有意义。

本篇文章会简单介绍与抛出异常相关的内容,包括关键的函数及结构体。下一篇文章会通过实例介绍几种典型情况(有调试符号 / 没有调试符号 / 32 位程序 / 64 位程序)下的定位方法。

阅读全文 »

调试实战 | 谁在偷偷占用我的文件?原来是我自己

发表于 2023-12-24 | 更新于: 2026-02-17 | 分类于 调试实战
字数统计: | 阅读时长 ≈ 分钟

缘起

之前基于 .net 官方提供的 FileSystemWatcher 写了一个文件变化监听工具,具体参考这篇文章 。主要解决了以下三个问题:

  1. 事件触发时,文件可能还不能被访问。
  2. 如果监听选项设置的过多,有可能会多次触发文件变化事件。
  3. 监听过滤器不够灵活,我没找到同时监听多种特定文件类型的方法(比如,同时只监听 .docx 和 .bmp 文件)。

为了解决问题1,我在调用用户注册的回调函数前,会先调用 WaitUntilCanAccess() 来确保文件是可访问状态。没想到在测试过程中发现了一个意想不到的问题。本文记录了解决这个问题的过程。

阅读全文 »

开发常识 | 彻底理清 CreateFile 读写权限与共享模式的关系

发表于 2023-12-23 | 更新于: 2026-02-17 | 分类于 开发
字数统计: | 阅读时长 ≈ 分钟

前言

前一阵子,我在编写文件变化监控程序的时候遇到了文件被占用的问题。很早之前写过一篇关于 CreateFile 函数的 dwDesiredAccess 和 dwShareMode 参数的笔记。我发现之前的理解不够全面、准确。为了更好的理解这两个参数的作用,我搜索了大量资料,编写了测试程序及测试脚本,参考了 xp 源码,终于搞清楚这两个参数的作用。简而言之,需要遵循以下两个规则:

规则 1:后续的访问权限与先前的共享模式不能冲突。

规则 2:后续的共享模式与先前的访问权限不能冲突。

如果你对下面的几个问题有明确的答案并且清楚的知道原因,那么可以跳过本文了。

  1. 第一次以读访问权限,写共享模式打开文件,会成功吗?
  2. 如果第一次打开成功了,第二次以写访问权限,读共享模式打开。会成功吗?
  3. 如果第二次打开成功了,第三次以读 / 写 / 读写访问权限,读写共享模式打开,会成功吗?
  4. 第一次以读访问权限,写共享模式打开文件,第二次以写访问权限,读写共享模式打开。第三次以写访问权限,读写共享模式打开,会成功吗?
阅读全文 »

如何确定线程栈的基址?

发表于 2023-10-29 | 更新于: 2026-02-17 | 分类于 调试实战 , 工具
字数统计: | 阅读时长 ≈ 分钟

缘起

很早之前,我遇到过几个与栈相关的问题,当时总结过几篇关于线程栈的文章,分别是 《栈大小可以怎么改?》、《栈局部变量优化探究,意外发现了 vs 的一个 bug ?》、《栈又溢出了》、《有趣的异常》。在这几篇总结中,简单的总结了栈溢出的原因,设置线程栈大小的方法。但是还有一点没弄清楚:操作系统是怎么知道一个线程的栈大小的?一定记录在某个位置了,否则就不能正确的在栈溢出的时候抛出异常了。不能根据 PE 头中的字段判断,因为在创建线程的时候可以指定线程栈大小。TEB 中的 StackLimit 是真正的栈底吗?带着这些疑问一起来刨根问底吧~

友情提示:结论在文章末尾。

阅读全文 »

调试实战 | 记一次有教益的 MFC 程序崩溃分析

发表于 2023-10-22 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

实际项目中,resource.h 文件中的控件太多太乱了,合并代码的时候非常痛苦。为了解决这个问题,需要对 resource.h 中的 ID 进行整理。根据之前整理的成果,很快把控件 ID 按对话框分类整理好了。没想到测试的时候遇到了各种崩溃,废了好大劲儿才解决。究其原因,是对 MFC 资源管理机制认识不够深刻。尝试创建某个模块内的对话框的时候,意外地找到了其它模块中的对话框资源。MFC 到底是怎么查找资源模块的呢?应该如何排查这种问题呢?一起来看看吧。

阅读全文 »

如何查找已注册消息的名称?

发表于 2023-10-22 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

TL;DR

在 Windows 中,通过 RegisterWindowMessage() 注册的消息,其消息 ID 在 0xC000 ~ 0xFFFF 之间。可以使用 GetClipboardFormatName() 根据消息 ID 反向查找已注册消息的名称。

阅读全文 »

调试实战 | 解决另外一个链接错误

发表于 2023-10-22 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

最近,在加班的过程中遇到一个链接错误 —— fatal error LNK1120: 1 unresolved externals。这种错误是老朋友了,对我这种常年写 bug 的老手来说,完全不是事儿,轻松+愉快。

根据以下的排查思路基本上能解决大多数链接错误:

既然报了链接错误,说明编译已经通过了,问题基本出现在库文件上。

有可能是找不到库文件(缺少库,或者库文件搜索路径不对),可以先确认工程配置是否正确或者使用 /verbose:lib 查看链接过程。

也可能是库文件不对(没包含对应的导出符号),可以通过 dumpbin /exports error.lib > error.txt 查看 lib 库中的导出符号。按照以上步骤排查基本上可以解决绝大多数链接错误。

好的,让我们一起来实战一下吧。

阅读全文 »

一个 32 位程序的用户空间区域可以有多大?

发表于 2023-10-22 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

我在《调试实战 | 记一次有教益的内存碎片转储文件分析》中分析了一个由于内存碎片导致的崩溃转储。发现一个很“奇怪”的现象——程序是 32 位的,但是在查看堆空间大小的时候,居然有将近 4GB。

相信各位小伙伴儿应该听过下面这种说法:32 位进程有 4GB 的虚拟内存,其中低 2GB 是用户空间,应用程序可以访问,高 2GB 是内核空间,应用程序不能访问,但是内核可以访问。在系统开启 /3GB 的情况下,用户空间可以提升至 3GB,内核空间被压缩到 1GB。

这也是我学到的关于 32 位进程虚拟内存相关的知识,但是上述描述不够准确。比如,开启了 /3GB,进程用户空间就一定可以提升至 3GB 吗?在 64 位系统上,上述说法还成立吗?

带着上述疑问,我翻看了微软官方文档,本文尽可能全面的总结进程的虚拟内存空间划分。因为能力有限,本文只涉及 x86/x64 平台,不涉及 ARM 平台。

阅读全文 »

调试实战 | 调试一个由内存分配失败导致的崩溃

发表于 2023-10-17 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

前一阵子,朋友开发的 QT 程序遇到了一个崩溃问题,抓了 dump 。我跟着一起分析的。最后发现是由于内存分配失败导致的。

一起来练习一下排查思路吧。

阅读全文 »

调试实战 | 记一次有教益的 vs2022 内存分配失败崩溃分析(续)

发表于 2023-10-16 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

前言

前一阵子遇到了 vs2022 卡死的问题,在上一篇文章中重点分析了崩溃的原因 —— 当 vs2022 尝试分配 923MB 的内存时,物理内存+页文件大小不足以满足这次分配请求,于是抛出异常。

本篇文章将重点挖掘一下 vs2022 在崩溃之前已经分配的内容。

说明: 本文很早就写了草稿,一直没时间整理发布,Finally~

阅读全文 »

调试实战 | 记一次有教益的 vs2022 内存分配失败崩溃分析

发表于 2023-10-15 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

前言

之前一直以为 64 位进程很难出现内存分配异常,因为 64 位进程的虚拟内存空间非常大(总共 64 位,目前只用了 48 位,也就是 256TB,用户态可以使用一半,也就是 128TB)。没想到,前一阵子居然遇到了 vs2022( vs 终于有了 64 位的版本)分配内存失败的情况。分析到最后是因为分配 MEM_COMMIT 类型的内存失败导致的异常。一起来看看吧。

说明: 本文很早就写了草稿,一直没时间整理发布,Finally~

阅读全文 »

如何判断转储文件是在 32 位系统还是 64 位系统下生成的?

发表于 2023-10-15 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

缘起

曾经在 2022 年分析过一个崩溃转储,文章在这里。在那个案例中,堆空间大小将近 4GB。根据上次的结论,这应该是一个运行在 64 位系统下的 32 位进程崩溃产生的转储文件。这让我有了一个疑问?怎么从进程转储文件中得知进程是 32 位的还是 64 位的?如果是 32 位进程,怎么判断是运行在 32 位系统上还是运行在 64 位系统上呢?

阅读全文 »

如何快速从 32 位转储文件中找到异常发生时的线程上下文

发表于 2023-10-15 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

前言

在上一篇文章中介绍了如何在 64 位进程的转储文件中查找异常上下文的方法 —— KiUserExceptionDispatcher() 函数对应栈帧的 Child-SP 的值保存了异常发生时的线程上下文。

本文将介绍如何在 32 位进程及 wow64 进程的转储文件中查找异常上下文的方法,并且会先介绍几个跟异常分发相关的函数和结构体。如果忘记了结论,可以根据函数参数手动逆向查找验证。

阅读全文 »

如何快速从 64 位转储文件中找到异常发生时的线程上下文

发表于 2023-09-29 | 更新于: 2026-02-17 | 分类于 调试
字数统计: | 阅读时长 ≈ 分钟

前言

经常做调试的朋友可能会遇到在 windbg 里通过 k 系列命令得到的调用栈没有太大参考意义。一般是由于线程上下文不对导致的。这时候可以通过 !analyze -v 让 windbg 自动帮我们分析出正确的调用栈及异常发生时的线程上下文。有了上下文信息,就可以执行 .cxr address_to_context 命令切换上下文,这时候再通过 k 命令查看调用栈,一般可以得到一个有意义的调用栈。

但有时候 !analyze -v 分析出来的上下文信息也是不对的。这时候就需要我们自己手动查找异常上下文了。

这不,最近我就遇到了一个需要手动查找异常上下文的情况。经过调查发现了一个非常重要的规律 —— 64 位程序中,KiUserExceptionDispatcher 函数对应栈帧的 Child-SP 的值保存了异常发生时的线程上下文。

本文完整记录了整个查找验证的过程。

吐槽: 64 位程序的参数传递方式与 32 位程序大不相同,不能根据 ebp 定位参数了。而是需要结合反汇编代码来推断某个函数的参数是否保存到栈上。如果没保存到栈上,基本上很难找到相关参数了。

阅读全文 »
123…8
BianChengNan

BianChengNan

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