缘起
在上篇文章《调试实战 | 调试另外一个由于全局变量初始化顺序导致的 dll 加载失败问题(上)》中,解决了由于全局变量初始化顺序不对导致的崩溃问题。但是代码里还有一处非常隐蔽的 bug,今天继续介绍一下这个问题及对应的解决方法。
在上篇文章《调试实战 | 调试另外一个由于全局变量初始化顺序导致的 dll 加载失败问题(上)》中,解决了由于全局变量初始化顺序不对导致的崩溃问题。但是代码里还有一处非常隐蔽的 bug,今天继续介绍一下这个问题及对应的解决方法。
最近又遇到了一个程序功能不正常的问题,深入调查后发现与全局变量初始化顺序有非常大的关系,只不过这次更加隐蔽。
之前总结了两篇与全局变量初始化顺序有关的文章,感兴趣的小伙伴儿可以参考《调试实战 | dll 加载失败之全局变量初始化篇》 和 《调试实战 | 全局变量初始化顺序探究》。
回顾整个 2023 ,相比 2022 加班少了,工作没那么拼命了。
由于各种原因,年初立的 flag 好几个都没实现。
rust,但远没有达到能实战的地步我对 2024 这个数字感到非常亲切,2024 = 1000 + 1024,两个一千
今年的首要任务依旧是锻炼身体
本来计划 2024 年跑步作为日常锻炼的方式,结果 2023 年最后一次从公司跑回家后膝盖疼,2024 只能偶尔跑跑了
2023 花费了很大一部分时间在遛狗上,2024 继续努力。如果不是狗子的陪伴,估计我早抑郁了,感谢,感恩。坚持练习英语口语
希望这次不要因为任何原因中断
继续坚持分享技术文章
遇到值得总结的问题,及时总结分享
读一些非技术书籍
之前看的书以技术书籍为主,2024 年争取多看些非技术的书籍
做一些改变
尽量熟悉 AI 相关的人和事
前一阵子,我在编写文件变化监控程序的时候遇到了文件被占用的问题。很早之前写过一篇关于 CreateFile 函数的 dwDesiredAccess 和 dwShareMode 参数的笔记。我发现之前的理解不够全面、准确。为了更好的理解这两个参数的作用,我搜索了大量资料,编写了测试程序及测试脚本,参考了 xp 源码,终于搞清楚这两个参数的作用。简而言之,需要遵循以下两个规则:
规则 1:后续的访问权限与先前的共享模式不能冲突。
规则 2:后续的共享模式与先前的访问权限不能冲突。
如果你对下面的几个问题有明确的答案并且清楚的知道原因,那么可以跳过本文了。
很早之前,我遇到过几个与栈相关的问题,当时总结过几篇关于线程栈的文章,分别是 《栈大小可以怎么改?》、《栈局部变量优化探究,意外发现了 vs 的一个 bug ?》、《栈又溢出了》、《有趣的异常》。在这几篇总结中,简单的总结了栈溢出的原因,设置线程栈大小的方法。但是还有一点没弄清楚:操作系统是怎么知道一个线程的栈大小的?一定记录在某个位置了,否则就不能正确的在栈溢出的时候抛出异常了。不能根据 PE 头中的字段判断,因为在创建线程的时候可以指定线程栈大小。TEB 中的 StackLimit 是真正的栈底吗?带着这些疑问一起来刨根问底吧~
友情提示:结论在文章末尾。
在 Windows 中,通过 RegisterWindowMessage() 注册的消息,其消息 ID 在 0xC000 ~ 0xFFFF 之间。可以使用 GetClipboardFormatName() 根据消息 ID 反向查找已注册消息的名称。
最近,在加班的过程中遇到一个链接错误 —— fatal error LNK1120: 1 unresolved externals。这种错误是老朋友了,对我这种常年写 bug 的老手来说,完全不是事儿,轻松+愉快。
根据以下的排查思路基本上能解决大多数链接错误:
既然报了链接错误,说明编译已经通过了,问题基本出现在库文件上。
有可能是找不到库文件(缺少库,或者库文件搜索路径不对),可以先确认工程配置是否正确或者使用 /verbose:lib 查看链接过程。
也可能是库文件不对(没包含对应的导出符号),可以通过 dumpbin /exports error.lib > error.txt 查看 lib 库中的导出符号。按照以上步骤排查基本上可以解决绝大多数链接错误。
好的,让我们一起来实战一下吧。
我在《调试实战 | 记一次有教益的内存碎片转储文件分析》中分析了一个由于内存碎片导致的崩溃转储。发现一个很“奇怪”的现象——程序是 32 位的,但是在查看堆空间大小的时候,居然有将近 4GB。
相信各位小伙伴儿应该听过下面这种说法:32 位进程有 4GB 的虚拟内存,其中低 2GB 是用户空间,应用程序可以访问,高 2GB 是内核空间,应用程序不能访问,但是内核可以访问。在系统开启 /3GB 的情况下,用户空间可以提升至 3GB,内核空间被压缩到 1GB。
这也是我学到的关于 32 位进程虚拟内存相关的知识,但是上述描述不够准确。比如,开启了 /3GB,进程用户空间就一定可以提升至 3GB 吗?在 64 位系统上,上述说法还成立吗?
带着上述疑问,我翻看了微软官方文档,本文尽可能全面的总结进程的虚拟内存空间划分。因为能力有限,本文只涉及 x86/x64 平台,不涉及 ARM 平台。
经常做调试的朋友可能会遇到在 windbg 里通过 k 系列命令得到的调用栈没有太大参考意义。一般是由于线程上下文不对导致的。这时候可以通过 !analyze -v 让 windbg 自动帮我们分析出正确的调用栈及异常发生时的线程上下文。有了上下文信息,就可以执行 .cxr address_to_context 命令切换上下文,这时候再通过 k 命令查看调用栈,一般可以得到一个有意义的调用栈。
但有时候 !analyze -v 分析出来的上下文信息也是不对的。这时候就需要我们自己手动查找异常上下文了。
这不,最近我就遇到了一个需要手动查找异常上下文的情况。经过调查发现了一个非常重要的规律 —— 64 位程序中,KiUserExceptionDispatcher 函数对应栈帧的 Child-SP 的值保存了异常发生时的线程上下文。
本文完整记录了整个查找验证的过程。
吐槽:
64位程序的参数传递方式与32位程序大不相同,不能根据ebp定位参数了。而是需要结合反汇编代码来推断某个函数的参数是否保存到栈上。如果没保存到栈上,基本上很难找到相关参数了。