Windows堆栈限制检查回顾,后续

The Old New Thing (Raymond Chen) 新闻

摘要

Raymond Chen跟进了他之前关于ARM64堆栈限制检查的文章,指出了堆栈探测函数中x15寄存器的非常规使用细节,并比较了多个架构的寄存器使用。

<p><a href="https://aarongiles.com/"> Aaron Giles</a> 参与了将Windows移植到ARM32和AArch64的工作,他<a href="https://bsky.app/profile/did:plc:rozrlqzq7umwvv5etd7slmxz/post/3mhk2isogwc2q?ref_src=embed">指出</a>我之前的<a title="Windows stack limit checking retrospective: arm64, also known as AArch64" href="https://devblogs.microsoft.com/oldnewthing/20260320-00/?p=112154">ARM64堆栈限制检查回顾</a>中遗漏了一个细节:</p> <blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:rozrlqzq7umwvv5etd7slmxz/app.bsky.feed.post/3mhk2isogwc2q" data-bluesky-cid="bafyreihi4tton7infgddx7csbqjm5xljewdwpfeoi27narlqdbfghlsp2a" data-bluesky-embed-color-mode="system"> <p lang="en">每隔一段时间,Raymond Chen就会发表一系列架构对比文章,我就能看到(经过转述的)自己很久以前写的代码。他关于我们为何传递stack size/16的解释是正确的,但让我惊讶的是他没有提到非常规的x15使用。</p> <p>— Aaron Giles (<a href="https://bsky.app/profile/did:plc:rozrlqzq7umwvv5etd7slmxz?ref_src=embed">@aarongiles.com</a>) <a href="https://bsky.app/profile/did:plc:rozrlqzq7umwvv5etd7slmxz/post/3mhk2isogwc2q?ref_src=embed"> 2026年3月20日 晚上8:08</a></p></blockquote> <p><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script></p> <p>我猜“非常规的x15使用”指的是:“为什么参数是通过<code>x15</code>寄存器传递的?AArch64调用约定将第一个参数放在<code>x0</code>寄存器中,那么这个参数不应该是<code>x0</code>寄存器吗?”</p> <p>我觉得这太明显了,所以认为不值得提及。</p> <p>需要执行堆栈探测的函数陷入了一个两难境地:它有自己的入站参数,其中一些可能通过寄存器传递。如果堆栈大小参数像普通参数一样传递给堆栈探测函数,那么调用函数必须将原始的入站参数保存到某处。但它不能将它们保存到堆栈上,因为在使用堆栈之前必须先进行堆栈探测。</p> <p>解决方案是为堆栈探测函数提供自定义调用约定,将其限制在未用于接收入站参数的临时寄存器。</p> <table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3"> <tbody> <tr> <th>架构</th> <th>用于参数</th> <th>分配大小</th> <th>同时修改</th> </tr> <tr> <td>8086</td> <td>&nbsp;</td> <td><tt>ax</tt></td> <td><tt>bx</tt>, <tt>dx</tt></td> </tr> <tr> <td>x86-32</td> <td><tt>ecx</tt></td> <td><tt>eax</tt></td> <td>&nbsp;</td> </tr> <tr> <td>MIPS</td> <td><tt>a0</tt>…<tt>a3</tt></td> <td><tt>t8</tt></td> <td>&nbsp;</td> </tr> <tr> <td>PowerPC</td> <td><tt>r3</tt>…<tt>r10</tt></td> <td><tt>r12</tt></td> <td><tt>r0</tt>, <tt>r11</tt></td> </tr> <tr> <td>Alpha AXP</td> <td><tt>a0</tt>…<tt>a5</tt></td> <td><tt>t12</tt></td> <td><tt>t8</tt>, <tt>t9</tt>, <tt>t10</tt></td> </tr> <tr> <td>x86-64</td> <td><tt>rcx</tt>, <tt>rdx</tt>, <tt>r8</tt>, <tt>r9</tt></td> <td><tt>rax</tt></td> <td><tt>r10</tt>, <tt>r11</tt></td> </tr> <tr> <td>AArch64</td> <td><tt>x0</tt>…<tt>x7</tt></td> <td><tt>x15</tt></td> <td><tt>x16</tt>, <tt>x17</tt></td> </tr> </tbody> </table> <p>处理器架构的调用约定会将某些寄存器指定为“超易失”,通常是那些保留给汇编临时变量或用于模块间函数调用的寄存器。这些寄存器非常适合堆栈探测函数使用,因为它们绝对不可能用于普通参数传递。</p> <p>例如,PowerPC使用<tt>r11</tt>,AArch64使用<tt>r16</tt>和<tt>r17</tt>,它们都可用于函数胶水桩。其他机会则被忽略了:MIPS和Alpha AXP本可以使用<tt>at</tt>,不过我能理解他们可能想避免使用它,因为汇编器在汇编伪指令时可能会隐式地使用它们。</p> <p>本文章<a href="https://devblogs.microsoft.com/oldnewthing/20260617-00/?p=112436">Windows堆栈限制检查回顾,后续</a>首发于<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/06/18 12:56

# Windows 堆栈限制检查回顾,后续篇 - 《老调新谈》 来源:https://devblogs.microsoft.com/oldnewthing/20260617-00?p=112436 Aaron Giles (https://aarongiles.com/) 曾参与将 Windows 移植到 ARM32 和 AArch64 的工作。他指出了我在关于 arm64 堆栈限制检查的回顾(https://devblogs.microsoft.com/oldnewthing/20260320-00/?p=112154)中遗漏的一个细节(https://bsky.app/profile/did:plc:rozrlqzq7umwvv5etd7slmxz/post/3mhk2isogwc2q?ref_src=embed): > 每隔一段时间,Raymond Chen 就会写一篇架构比较系列文章,而我也总能看到(改述版)一些我很久以前写过的代码。他关于我们为什么传递 stack size/16 的解释是正确的,但令我惊讶的是他没有提及那个非常规的 x15 用法。—— Aaron Giles (@aarongiles.com (https://bsky.app/profile/did:plc:rozrlqzq7umwvv5etd7slmxz?ref_src=embed)),2026年3月20日晚上8:08 (https://bsky.app/profile/did:plc:rozrlqzq7umwvv5etd7slmxz/post/3mhk2isogwc2q?ref_src=embed) 我猜Aaron所说的“非常规x15用法”是指:“为什么参数是通过 `x15` 寄存器传递的?AArch64 调用约定规定第一个参数应通过 `x0` 寄存器传递,所以这个参数不是应该放在 `x0` 里吗?” 对我来说这太明显了,以至于我觉得不值得一提。 需要进行堆栈探测的函数处境有点尴尬:它拥有入站参数,其中一些可能通过寄存器传递。如果堆栈大小参数像普通参数一样传递给堆栈探测函数,那么调用函数就必须将其原始的入站参数暂存到其他地方。但它不能将这些参数保存到堆栈上,因为在使用堆栈之前必须先进行堆栈探测。 解决方案是给堆栈探测函数一个自定义的调用约定,将其限制在那些不用于接收入站参数的临时寄存器中。 | 架构 | 用于参数的寄存器 | 分配大小的寄存器 | 同时被修改的寄存器 | |------|----------------|------------------|-------------------| | 8086 | ax | bx | dx | | x86-32 | ecx | eax | | | MIPS | a0...a3 | t8 | | | PowerPC | r3...r10 | r12 | r0, r11 | | Alpha AXP | a0...a5 | t12 | t8, t9, t10 | | x86-64 | rcx, rdx, r8, r9 | rax | r10, r11 | | AArch64 | x0...x7 | x15 | x16, x17 | 处理器架构的调用约定将某些寄存器指定为“超易失”寄存器,这些寄存器通常保留给汇编器临时变量或用于模块间的函数调用。这些寄存器非常适合堆栈探测函数使用,因为它们不可能用于常规参数传递。 例如,PowerPC 使用 r11,AArch64 使用 r16 和 r17,所有这些寄存器都可用于函数胶合存根。其他架构则错失了机会:MIPS 和 Alpha AXP 本可以使用 `at`,不过我明白为什么他们可能不想使用它们,因为汇编器在汇编伪指令时可能会隐式使用这些寄存器。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与 Windows 的发展已有30多年。2003年,他创办了一个名为《老调新谈》的网站,其受欢迎程度远超他最狂野的想象,这一发展至今仍让他感到不安。该网站还衍生出了一本书,巧合的是书名也是《老调新谈》(Addison Wesley 2007)。他偶尔会出现在 Windows Dev Docs Twitter 账号上,讲述一些毫无实用信息的故事。

相似文章

SBCL: 终极汇编代码面包板 (2014)

Hacker News Top

一篇技术博客文章,探讨如何使用SBCL作为汇编代码的面包板,重点介绍基于堆栈的虚拟机技术,如旋转堆栈和高效的原语操作分发,并引用了F18处理器和x87堆栈。

编写可移植的ARM64汇编代码

Hacker News Top

一份关于编写可在Apple Darwin和Linux/BSD系统间移植的ARM64汇编代码的指南,涵盖ABI、符号命名和向量助记符的差异。

关于WebAssembly作为栈机器的思考

Eli Bendersky

这篇博客文章回应了关于WebAssembly不是纯栈机器的说法,通过讨论其带局部变量的设计并与Forth进行比较,论证它仍然符合栈机器的定义,并且其类似寄存器的局部变量提高了可读性和性能。