根据masm的macamd64.inc,rex_push_reg
,
...rex_push_reg must be used in lieu of push_reg when it appears as the first instruction in a function, as the calling standard dictates that functions must not begin with a single byte instruction.
但是,我找不到任何说明这一点的文档。这是真的?它在哪里记录?为什么会这样?
最佳答案
这个声明的执行部分似乎是“调用标准”——哪个调用标准?这个笑话可能是老生常谈了,但它仍然很贴切:标准的好处在于有太多可供选择的标准。
在这种情况下,既然你说的是 MASM,我们可以假设目标平台是 Windows,所以 Windows 64-bit calling convention将被假定,而不是官方 AMD64 规范中的内容。但是,和您一样,我在那里找不到任何符合此要求的内容。
不过,我认为这条评论所指的是 Microsoft 的内部标准,旨在允许对系统二进制文件进行热修补。 “热修补”是指能够动态修补内存中的二进制文件——例如以应用系统更新——而无需重新启动。
这个工作的最低要求是在每个函数的开头有一个 2 字节的短 JMP
指令的空间。 (请注意,短跳转仅允许执行从当前指令指针的 −128 到 +127 字节之间的任何位置,但这足以分支到 long 跳转,然后分支到修补函数由更新提供。在实践中,长跳转指令被修补到函数之间的填充中。)
因此,函数不能以 1 字节指令开头,因为热补丁可能会导致指令指针指向指令的中间。 (想想多线程竞争条件。)所以规则是,如果你想用像 PUSH RBP
这样通常只有 1 个字节的序言指令开始一个函数,你需要添加一个 1 -byte REX 前缀。这个不必要的 REX 前缀被 CPU 忽略,本质上作为一个 1 字节的 NOP。
在 32 位版本中,热补丁由 2 字节指令 MOV EDI, EDI
提供。这会将 EDI
寄存器复制到自身而不影响标志,因此它实际上是一个 NOP。
对于 32 位构建,您必须专门传递 /hotpatch
switch给编译器让它插入这条指令。然而,在 64 位构建中,编译器总是像指定了 /hotpatch
一样工作,因此第一条指令长度为 2 个字节的要求实际上成为平台标准的一部分。
那么,为什么要制定这个复杂的规则而不是让编译器在每个函数的开头插入一个 2 字节的 NOP,就像在 32 位构建中所做的那样?好吧,我不能肯定地说,但我可以推测。一个问题是 MOV EDI, EDI
不是 x64 上的 NOP,因为它隐式地将 RDI
寄存器的高 32 位清零。您必须选择与 NOP 不同的指令,一旦您选择了该指令,您不妨重新考虑整个业务。其次,你为在那里使用 NOP 付出了(轻微的)性能成本,并且由于长模式下的大多数指令至少有 2 个字节长,因此要求无意义的 NOP 指令似乎不值得当通常存在的指令足够时,只有少数异常(exception)。
关于windows - 使用单字节指令开始 x64 函数是否合法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44563484/