【问题标题】:What is the purpose of the 40h REX opcode in ASM x64?ASM x64 中 40h REX 操作码的目的是什么?
【发布时间】:2023-03-11 14:32:01
【问题描述】:

我一直在尝试了解 ASM x64 指令的 0x40 REX 操作码的用途。例如,在这个来自 Kernel32.dll 的函数序言中:

如您所见,他们使用push rbx 作为:

40 53      push        rbx 

但仅使用 53h 操作码(不带前缀)也会产生相同的结果:

根据this site,REX前缀的布局如下:

所以40h 操作码似乎什么也没做。有人能解释一下它的目的吗?

【问题讨论】:

  • 这里似乎有两个问题:1)它有什么作用。 2)为什么会在那里?它的作用(根据我正在阅读的参考资料)什么都不是。那么,它为什么会在那里?我的第一个猜测与 Nathan 的相同:某种对齐/填充。但是我在该代码中看不到任何可以从那里的对齐中受益的东西。所以,这是一个理论:通过 kernel32.dll 进行分页,有很多 nops。这几乎就像有人试图将某些代码保留在特定地址。所以也许 rex push rbx 被修补了一些短 1 字节的代码?
  • 这很奇怪,push rbx 有 64 位操作数大小,所以如果他们要使用 REX 前缀填充,它应该使用 REX.W=1 (0x48)(不需要,因为 @987654339 @ 已经默认为 64 位操作数大小)。我想这证实了 REX.W=0 被所有现有 CPU 安全地忽略为 push,但是,如果你在 Windows 上的 kernel32.dll 中找到它。哦,NASM 将push r12 编码为41 54,即使用 REX.W=0,B=1`。显然我需要在How many bytes does the push instruction pushes onto the stack when I don't specify the operand size? 上更新我的答案
  • 该网站的链接已损坏。
  • @duru,链接现在是X86-64 Instruction Encoding

标签: windows 64-bit x86-64 disassembly opcode


【解决方案1】:

04xh 字节(即040h041h...04fh)确实是 REX 字节。正如您在问题中列出的那样,低半字节中的每一位都有一个含义。值040h 表示REX.WREX.RREX.XREX.B 都是0。这意味着添加此字节不会对该指令执行任何操作,因为您没有覆盖 any 默认 REX 位,并且它不是 AH/BH/CH/DH 作为 8 位指令一个操作数。

此外,XRB 位都对应一些操作数。如果您的指令不使用这些操作数,则相应的 REX 位将被忽略。

【讨论】:

  • 是的,我知道。那么为什么要像我在上面展示的第一个函数中那样使用它呢?
  • 或者,他们的编译器是否使用40h 操作码作为某种对齐nop-type 填充符?
  • @HansPassant:哈,有趣。 This (somewhat old) article on hotpatching 解释了在函数开头使用 nop 类型指令的目的。尽管在我的示例中,40 53 push rbx 指令不仅仅是一个无用的指令,就像五个 nops 或 mov edi, edi 一样(在那篇文章中给出)。它实际上是有目的的。它只比它应该的长一个字节。我错过了什么吗?
  • @c00000fd:是的,你错过了一些东西。 Microsoft 使用冗余的 REX 前缀来延长指令而不是使用单独的 NOP 指令。这使代码运行得更快。 What methods can be used to efficiently extend instruction length on modern x86?。当您进行热补丁时,您将一些早期指令替换为 jmp 到新代码,然后可能会跳回函数的其余部分。您与 5x 单字节 nop 指令链接的那篇文章是一个糟糕的计划;当您将其替换为 jmp 时,可能会在第二次执行 nop
  • @Nathan: 0x40 对字节寄存器有影响:例如,需要对 mov al, sil 进行编码。 (这就是为什么 AH/BH/CH/DH 不能在带有 REX 前缀的指令中编码,所以你不能编码 mov ah, sil)但是是的,对于 push 和除 8 位操作数大小以外的任何操作码说明,0x40 是多余的。
【解决方案2】:

我称之为虚拟 REX 前缀,因为它在 push 或 pop 之前什么都不做。我想知道这是否被允许,你的经验表明它是允许的。

之所以存在,是因为微软的人显然生成了上述代码。我推测对于额外的寄存器是必需的,所以他们总是生成它,并且在不需要时不会费心将其删除。另一种可能性是指令的延长对调度和/或对齐有微妙的影响,并且可以使代码更快。这当然需要对特定处理器有详细的了解。

我正在研究机器代码的优化器。虚拟前缀很有帮助,因为它们使代码更加统一;需要考虑的案例较少。然后作为最后一步,可以删除多余的前缀。

【讨论】:

  • 在这种情况下,唯一的性能优势是作为单独的 long-nop 指令的替代方案,以给热补丁替换一些东西,这会更糟。见comments on the other answer。使指令更长并没有太大的伤害,但确实会使 I-cache 占用空间膨胀,并且可能意味着更糟糕的打包到 uop 缓存行中。如果 平均 指令长度在 32 个字节的机器代码块中小于 2,则一些填充可能很好,但它不在这里。
  • 我正在查看使用 VS 2008 SP2 生成的东西的反编译,它有 14,621 个以 40 53 48 83 EC 开头的序言(push rbx; sub rsp, x 用于除彼得以外的其他人)。尽管他们会很乐意使用44 55 53 56 57 ...在我看来,您的热补丁答案是正确的。此外,只有大约 500 个函数的序言以 1 字节指令开头,所有这些(快速示例)实际上都不是函数。函数总数约为 110,000 所以......是的。这对逆向工程师来说无疑是一个好处:)
猜你喜欢
  • 2010-09-19
  • 1970-01-01
  • 1970-01-01
  • 2020-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-05
相关资源
最近更新 更多