如何在 Delphi 汇编器中使用“.align”来协调短条件跳转和分支目标对齐?

Max*_*tin 6 delphi x86 assembly memory-alignment

如何在 Delphi 汇编器中协调短条件跳转与分支目标对齐?

\n\n

I\xe2\x80\x99m 使用 Delphi 版本 10.2 Tokyo,针对 32 位和 64 位汇编,完全使用汇编编写一些函数。

\n\n

如果我不\xe2\x80\x99t 使用 .align,编译器会正确short编码条件跳转指令(2 字节指令,由 1 字节操作码074h和 1 字节相对偏移量 -+ 最多 07Fh 组成)。但是,如果我曾经放置过一个.align,即使是小到.align 4- 所有条件跳转指令都位于 .align 之前并且目标位于.align- 在这种情况下,所有这些指令都变成 6 字节指令,而不是 2 字节指令他们应该是。只有位于 .align 之后的指令仍被正确编码为 2 字节short

\n\n

Delphi 汇编器不接受 \xe2\x80\x99t 接受 \xe2\x80\x98short\xe2\x80\x99 前缀。

\n\n

如何协调短条件跳转与分支目标对齐.align在 Delphi 汇编器中协调短条件跳转与分支目标对齐?

\n\n

这是一个示例程序 \xe2\x80\x93 请注意,有一个.align这是一个示例程序 \xe2\x80\x93 请注意中间

\n\n
    procedure Test; assembler;\n    label\n      label1, label2, label3;\n    asm\n      mov     al, 1\n      cmp     al, 2\n      je      label1\n      je      label2\n      je      label3\n    label1:\n      mov     al, 3\n      cmp     al, 4\n      je      label1\n      je      label2\n      je      label3\n      mov     al, 5\n      .align 4\n    label2:\n      cmp     al, 6\n      je      label1\n      je      label2\n      je      label3\n      mov     al, 7\n      cmp     al, 8\n      je      label1\n      je      label2\n      je      label3\n    label3:\n    end;\n
Run Code Online (Sandbox Code Playgroud)\n\n

下面是它的编码方式 \xe2\x80\x93 条件跳转,位于 之前align,指向 label2 和 label3 (在align)被编码为 6 字节指令(这是 64 位 CPU 目标):

\n\n
0041C354 B001          mov al,$01      //   mov     al, 1\n0041C356 3C02          cmp al,$02      //   cmp     al, 2\n0041C358 740C          jz $0041c366    //   je      label1\n0041C35A 0F841C000000  jz $0041c37c    //   je      label2\n0041C360 0F8426000000  jz $0041c38c    //   je      label3\n0041C366 B003          mov al,$03 //label1: mov al, 3\n0041C368 3C04          cmp al,$04      //   cmp     al, 4\n0041C36A 74FA          jz $0041c366    //   je      label1\n0041C36C 0F840A000000  jz $0041c37c    //   je      label2\n0041C372 0F8414000000  jz $0041c38c    //   je      label3\n0041C378 B005          mov al,$05      //   mov     al, 5\n0041C37A 8BC0          mov eax,eax     //  <-- a 2-byte dummy instruction, inserted by ".align 4" (almost a 2-byte NOP)\n0041C37C 3C06          cmp al,$06 //label2: cmp al, 6\n0041C37E 74E6          jz $0041c366    //   je      label1\n0041C380 74FA          jz $0041c37c    //   je      label2\n0041C382 7408          jz $0041c38c    //   je      label3\n0041C384 B007          mov al,$07      //   mov     al, 7\n0041C386 3C08          cmp al,$08      //   cmp     al, 8\n0041C388 74DC          jz $0041c366    //   je      label1\n0041C38A 74F0          jz $0041c37c    //   je      label2\n0041C38C C3            ret        // label3:\n
Run Code Online (Sandbox Code Playgroud)\n\n

但如果我删除.align- 所有指令都有正确的大小 - 只是 2 个字节,因为它们曾经是:

\n\n
0041C354 B001          mov al,$01      //   mov     al, 1\n0041C356 3C02          cmp al,$02      //   cmp     al, 2\n0041C358 7404          jz $0041c35e    //   je      label1\n0041C35A 740E          jz $0041c36a    //   je      label2\n0041C35C 741C          jz $0041c37a    //   je      label3\n0041C35E B003          mov al,$03 //label1: mov     al, 3\n0041C360 3C04          cmp al,$04      //   cmp     al, 4\n0041C362 74FA          jz $0041c35e    //   je      label1\n0041C364 7404          jz $0041c36a    //   je      label2\n0041C366 7412          jz $0041c37a    //   je      label3\n0041C368 B005          mov al,$05      //   mov     al, 5\n0041C36A 3C06          cmp al,$06 //.align 4 label2:cmp al, 6\n0041C36C 74F0          jz $0041c35e    //   je      label1\n0041C36E 74FA          jz $0041c36a    //   je      label2\n0041C370 7408          jz $0041c37a    //   je      label3\n0041C372 B007          mov al,$07      //   mov     al, 7\n0041C374 3C08          cmp al,$08      //   cmp     al, 8\n0041C376 74E6          jz $0041c35e    //   je      label1\n0041C378 74F0          jz $0041c36a    //   je      label2\n0041C37A C3            ret             //   je      label3\n                                //  label3: \n
Run Code Online (Sandbox Code Playgroud)\n\n

回到条件跳转说明:如何协调短条件跳转与分支目标对齐.align在 Delphi 汇编器中协调短条件跳转与分支目标对齐?

\n\n

我承认在 SkyLake 及更高版本的处理器上对齐分支目标的好处很小,而且我知道我可以避免使用.align- 它还会节省代码大小。但我想知道如何使用 Delphi 汇编器来生成短跳转align. 此问题不仅在 64 位目标中存在,在 32 位目标中也存在。

\n

Pet*_*des 2

除非您的汇编器可以选择进行更好的分支位移优化(这可能需要重复传递),否则您可能会运气不好。(当然,您可以自己手动完成所有对齐,但每次更改任何内容时都必须重新完成。)

或者您可以使用不同的汇编器进行汇编。但正如我所料,这是非常不可取的,因为您无法访问 Delphi 特定的内容,例如在 asm 之外声明的内容的对象布局。(感谢@Rudy 的评论。)

您可以在 Delphi 汇编器中编写一些函数,并在那里尽可能多地执行 Delphi 特定的操作。在另一个汇编器中编写关键循环部分,hexdump 将其机器代码输出转储到db放置在 Delphi 程序集中间的伪指令中。

如果每个函数的开头至少与函数内的任何内容一样对齐,那么这可以正常工作,但您可能最终会浪费指令或将常量放入寄存器中供 NASM 部分使用,这可能比仅仅使用更糟糕更长的树枝。


仅位于 .align 之后的指令仍正确编码为 2 字节短指令

这不太准确。第一个je label1看起来不错,而且是在.align.

看起来任何跨越尚未评估的.align指令的分支都会为 留下空间rel32,并且汇编器永远不会返回并修复它。其他所有情况似乎都很好:向后分支跨越 a .align,向前分支不跨越 a .align


分支位移优化不是一个简单的问题,特别是当有.align指令时。不过,这似乎是一个真正次优的实现。

相关:为什么分支位移的“从小处开始”算法不是最佳的?有关汇编程序用于分支位移优化的算法的更多信息。即使优秀的汇编程序也可能无法做出最佳选择,尤其是在有.align指令的情况下。

  • @PeterCordes:Delphi 可以使用其他汇编器生成的目标文件,例如 TASM、MASM 或 NASM(也可能是 FASM 等,从未尝试过)。因此,人们可以使用另一种汇编器,但这意味着不需要许多使 Delphi 汇编器如此有用的功能,例如知道虚拟方法的 VMT 索引的指令,知道结构体的大小和结构体成员的偏移量,可以从其他 Delphi 模块导入内容,可以访问私有运行时例程等。即不做许多特定于 Delphi 的事情。如果这是唯一的问题... (2认同)
  • ...这还不足以让我转向 NASM。有一天,我确实将我的一个内置汇编程序源代码翻译为 NASM,但这很乏味。类型、结构等必须重新声明,VMTOFFSET 和其他指令丢失,外部标识符必须重新声明等。 (2认同)
  • @PeterCordes:“.ALIGN”插入特定于位数的代码,因此它可能不会在 64 位代码中插入“mov eax,eax”(如果大小不同或有一些副作用)。它将使用不同的东西(如有必要,使用第二个 NOP)并且无害。 (2认同)