以单字节指令开始 x64 函数是否合法?



根据masm的macamd64.inc,rex_push_reg

。push_reg当rex_push_reg显示为 函数中的第一条指令,如调用标准所指示的那样 该函数不得以单个字节指令开头。

但是,我找不到任何表达这一点的文档。这是真的吗?它在哪里记录?为什么会这样呢?

这种说法的操作部分似乎是"调用标准"——哪个调用标准?这个笑话可能是一个古老的笑话,但它仍然很贴切:标准的伟大之处在于有这么多可供选择。

在这种情况下,由于您说的是 MASM,我们可以假设目标平台是 Windows,因此将假定 Windows 64 位调用约定,而不是官方 AMD64 规范中的内容。但是,像您一样,我在那里找不到任何符合此要求的内容。

但是,我认为此评论所指的是Microsoft的内部标准,旨在允许对系统二进制文件进行热修补。"热修补"是指动态修补内存中的二进制文件的能力(例如,应用系统更新),而无需重新启动。

要使其正常工作的最低要求是在每个函数的开头都有空间修补一个 2 字节的短JMP指令。(请注意,短跳只允许从当前指令指针传递 −128 到 +127 字节的任意位置的执行,但这足以分支到跳,然后分支到更新提供的修补函数。在实践中,跳远指令被修补到函数之间的填充中。

因此,函数不能以 1 字节指令开头,因为热补丁可能会导致指令指针指向指令中间。(考虑多线程争用条件。所以规则是,如果你想用一个通常只有 1 个字节的序幕指令(如PUSH RBP)开始一个函数,你需要添加一个 1 字节的 REX 前缀。CPU 会忽略这个不必要的 REX 前缀,并且基本上充当 1 字节 NOP。

在 32 位版本中,热修补由 2 字节指令MOV EDI, EDI提供。这会将EDI寄存器复制到自身,而不会影响标志,因此它实际上是一个 NOP。

对于 32 位生成,必须专门将/hotpatch开关传递给编译器,才能使其插入此指令。但是,在 64 位构建中,编译器始终像指定了/hotpatch一样,因此第一条指令长度为 2 字节的要求实际上成为平台标准的一部分。

那么,为什么要制定这个复杂的规则,而不是让编译器在每个函数的开头插入一个 2 字节的 NOP,就像在 32 位构建中所做的那样?好吧,我不能肯定地说,但我可以推测。一个问题是MOV EDI, EDI不是x64 上的 NOP,因为它隐式将RDI寄存器的上 32 位归零。你必须选择一个不同的指令作为NOP,一旦你这样做了,你不妨重新考虑整个业务。其次,您为拥有该NOP而付出(轻微的)性能成本,并且由于长模式下的大多数指令至少为2字节长,因此当通常存在的指令足够时,似乎几乎不值得,只有少数例外。

最新更新