推测故障控制面板扩展如何截断了它面前的值
摘要
Raymond Chen 推测故障控制面板扩展如何因将64位指针截断为32位而导致崩溃,可能是由于64位移植过程中代码更新不完整。
<p>上一次,<a title="关闭显示控制面板时无效函数指针的案例" href="https://devblogs.microsoft.com/oldnewthing/20260715-00/?p=112535">我们发现控制面板扩展中的崩溃是由指针截断引起的</a>。代码手里明明有一个完好的64位指针,但不知为何失去理智,选择了丢弃高32位。</p>
<p>这种情况是怎么发生的?</p>
<p>我猜测这段代码最初是完好的32位代码:</p>
<pre>HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, GWL_WNDPROC, (LONG)g_originalWndProc);
</pre>
<p>然后他们将其重新编译为64位代码,并遇到了一个错误。</p>
<pre>error C2065: 'GWL_WNDPROC': undeclared identifier
</pre>
<p>然后他们查阅文档,发现对于64位Windows,<a title="系统窗口和类额外字节的演变" href="https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484"> <code>GWL_<wbr />WNDPROC</code> 被重命名为 <code>GWLP_<wbr />WNDPROC</code></a>。</p>
<p>于是他们通过将 <code>GWL_<wbr />WNDPROC</code> 改为 <code>GWLP_<wbr />WNDPROC</code> 来修复。</p>
<pre>HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, <span style="border: solid 1px currentcolor;">GWL_WNDPROC</span>, (LONG)g_originalWndProc);
</pre>
<p>然而,重命名该值的目的并不是为了烦你。重命名该值是为了提醒你注意可能发生指针截断的地方。在这个例子中,是最后一个参数,即原始的64位窗口过程。构建中断告诉你,你可能将一个32位值传递给了应该是64位的位置。在这个例子中,因为它被强制转换为 <code>(LONG)</code>。你应该将 <code>GWL_<wbr />WNDPROC</code> 升级为 <code>GWLP_<wbr />WNDPROC</code>,同时将强制转换从 <code>(LONG)</code> 升级为 <code>(LONG_PTR)</code>。</p>
<pre>HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, <span style="border: solid 1px currentcolor;">GWL_WNDPROC</span>, (<span style="border: solid 1px currentcolor;">LONG_PTR</span>)g_originalWndProc);
</pre>
<p>现在,这很可能是一个疏忽,而不是系统性错误,因为他们确实成功地正确子类化了窗口:</p>
<pre>WNDPROC g_originalWndProc;
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
g_originalWndProc = (WNDPROC)SetWindowLong(hwndButton, <span style="border: solid 1px currentcolor;">GWLP_WNDPROC</span>,
(<span style="border: solid 1px currentcolor;">LONG_PTR</span>)subclassWndProc);
</pre>
<p>他们只是遗漏了一处。也许是开发者在修复符号名称后分心了,忘记回来修复指针。</p>
<p>下次,我们将探讨为什么这个bug这么长时间都没有被修复。</p>
<p>本文 <a href="https://devblogs.microsoft.com/oldnewthing/20260716-00/?p=112539">《推测故障控制面板扩展如何截断了它面前的值》</a> 最初发表于 <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看缓存全文
缓存时间: 2026/07/16 22:54
# 推测有问题的控制面板扩展如何截断一个它本来就在手头的值 - 《老调重弹》
来源:https://devblogs.microsoft.com/oldnewthing/20260716-00?p=112539
上一次,我们发现了控制面板扩展崩溃的原因是指针截断(https://devblogs.microsoft.com/oldnewthing/20260715-00/?p=112535)。代码中明明有一个完好的 64 位指针,却不知为何大脑短路,选择把高 32 位给扔了。
这种情况是怎么发生的呢?
我的猜测是,这段代码最初是完好的 32 位代码:
``
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, GWL_WNDPROC, (LONG)g_originalWndProc);
``
然后他们将其重新编译为 64 位代码,结果报错。
``
error C2065: 'GWL_WNDPROC': undeclared identifier
``
于是他们翻回文档,发现对于 64 位 Windows,`GWL_WNDPROC` 已被重命名为 `GWLP_WNDPROC`(https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484)。
所以他们将 `GWL_WNDPROC` 改成了 `GWLP_WNDPROC` 来修复。
``
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, GWL_WNDPROC, (LONG)g_originalWndProc);
``
然而,重命名这个值的本意并不是为了烦你。重命名的目的是为了让你注意那些容易发生指针截断的地方。在这个例子中,就是最后一个参数——原始的 64 位窗口过程。构建中断是在告诉你,你可能把一个 32 位的值传给了本应是 64 位的地方。这里,是因为它被强制转换成了 `(LONG)`。你应该把 `GWL_WNDPROC` 升级为 `GWLP_WNDPROC`,同时把强制转换从 `(LONG)` 升级为 `(LONG_PTR)`。
``
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
SetWindowLong(hwndButton, GWL_WNDPROC, (LONG_PTR)g_originalWndProc);
``
这很可能是个疏忽,而非系统性故障,因为他们确实正确地对窗口进行了子类化:
``
WNDPROC g_originalWndProc;
HWND hwndButton = GetDlgItem(hdlg, ID_BUTTON);
g_originalWndProc = (WNDPROC)SetWindowLong(hwndButton, GWLP_WNDPROC,
(LONG_PTR)subclassWndProc);
``
他们只是漏掉了一个地方。可能是开发者在修改了符号名称后分了心,忘了回来处理这个指针。
下一次,我们将探讨为什么这个 bug 长期未修复。
## 分类
## 主题
## 作者
Raymond Chen
Raymond 已经参与 Windows 的演进超过 30 年。2003 年,他创办了名为《老调重弹》的网站,其受欢迎程度远超他最大胆的想象——这一发展至今仍让他感到毛骨悚然。该网站还衍生出了一本同名书籍(Addison Wesley 2007)。他偶尔会出现在 Windows Dev Docs 的 Twitter 账号上,讲述一些毫无实际信息的故事。
相似文章
关闭显示控制面板时无效函数指针的案例
这篇来自 The Old New Thing 的文章调查了 Windows 显示控制面板中一个常见的崩溃问题,该问题是由一个被截断为 32 位并进行符号扩展的无效函数指针引起的。作者通过分析崩溃转储来找出根本原因。
关于滥用Windows窗口类额外字节的兼容性说明
Raymond Chen 讨论了一个历史性的 Windows 兼容性问题,其中一些 16 位程序滥用窗口类额外字节来存储私有数据,以及微软如何在保持向后兼容性的同时,对 32 位和 64 位程序堵住了这个漏洞。
Windows堆栈限制检查回顾,后续
Raymond Chen跟进了他之前关于ARM64堆栈限制检查的文章,指出了堆栈探测函数中x15寄存器的非常规使用细节,并比较了多个架构的寄存器使用。
为什么显示控制面板指针截断错误长期未被修复?
本文解释了显示控制面板中与指针截断相关的错误为何长期存在,尽管供应商已经修复,但受限于计算机制造商的驱动程序认证流程,用户只能使用过时的认证驱动程序。
一个未正式卸载却从内存中消失的DLL案例,第二部分
Ray Chen的一篇技术博文,探究一个内存损坏bug:单个字节0x01破坏HMODULE句柄,导致DLL被错误释放,进而在进程终止时崩溃。