首页 文章

如何在Delphi汇编程序中将带有分支目标对齐的短条件跳转与`.align`进行协调?

提问于
浏览
4

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

我正在使用Delphi版本10.2东京,用于32位和64位汇编,完全使用汇编编写一些函数 .

如果我不使用 .align ,编译器会正确编码 short 条件跳转指令(2字节指令,包含1字节操作码 074h 和1字节相对偏移 - 最高07Fh) . 但是,如果我连一个 .align ,甚至小到 .align 4 - 所有条件跳转指令都位于.align之前并且目标位于 .align 之后 - 在这种情况下,所有这些指令都变为6字节指令,而不是2他们应该是.byte . 只有位于.align之后的指令才能正确编码为2字节 short .

Delphi Assembler不接受“短”前缀 .

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

这是一个示例程序 - 请注意中间有一个 .align .

procedure Test; assembler;
    label
      label1, label2, label3;
    asm
      mov     al, 1
      cmp     al, 2
      je      label1
      je      label2
      je      label3
    label1:
      mov     al, 3
      cmp     al, 4
      je      label1
      je      label2
      je      label3
      mov     al, 5
      .align 4
    label2:
      cmp     al, 6
      je      label1
      je      label2
      je      label3
      mov     al, 7
      cmp     al, 8
      je      label1
      je      label2
      je      label3
    label3:
    end;

以下是它的编码方式 - 位于 align 之前的条件跳转,指向label2和label3(在 align 之后)被编码为6字节指令(这是一个64位CPU目标):

0041C354 B001          mov al,$01      //   mov     al, 1
0041C356 3C02          cmp al,$02      //   cmp     al, 2
0041C358 740C          jz $0041c366    //   je      label1
0041C35A 0F841C000000  jz $0041c37c    //   je      label2
0041C360 0F8426000000  jz $0041c38c    //   je      label3
0041C366 B003          mov al,$03 //label1: mov al, 3
0041C368 3C04          cmp al,$04      //   cmp     al, 4
0041C36A 74FA          jz $0041c366    //   je      label1
0041C36C 0F840A000000  jz $0041c37c    //   je      label2
0041C372 0F8414000000  jz $0041c38c    //   je      label3
0041C378 B005          mov al,$05      //   mov     al, 5
0041C37A 8BC0          mov eax,eax     //  <-- a 2-byte dummy instruction, inserted by ".align 4" (almost a 2-byte NOP)
0041C37C 3C06          cmp al,$06 //label2: cmp al, 6
0041C37E 74E6          jz $0041c366    //   je      label1
0041C380 74FA          jz $0041c37c    //   je      label2
0041C382 7408          jz $0041c38c    //   je      label3
0041C384 B007          mov al,$07      //   mov     al, 7
0041C386 3C08          cmp al,$08      //   cmp     al, 8
0041C388 74DC          jz $0041c366    //   je      label1
0041C38A 74F0          jz $0041c37c    //   je      label2
0041C38C C3            ret        // label3:

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

0041C354 B001          mov al,$01      //   mov     al, 1
0041C356 3C02          cmp al,$02      //   cmp     al, 2
0041C358 7404          jz $0041c35e    //   je      label1
0041C35A 740E          jz $0041c36a    //   je      label2
0041C35C 741C          jz $0041c37a    //   je      label3
0041C35E B003          mov al,$03 //label1: mov     al, 3
0041C360 3C04          cmp al,$04      //   cmp     al, 4
0041C362 74FA          jz $0041c35e    //   je      label1
0041C364 7404          jz $0041c36a    //   je      label2
0041C366 7412          jz $0041c37a    //   je      label3
0041C368 B005          mov al,$05      //   mov     al, 5
0041C36A 3C06          cmp al,$06 //.align 4 label2:cmp al, 6
0041C36C 74F0          jz $0041c35e    //   je      label1
0041C36E 74FA          jz $0041c36a    //   je      label2
0041C370 7408          jz $0041c37a    //   je      label3
0041C372 B007          mov al,$07      //   mov     al, 7
0041C374 3C08          cmp al,$08      //   cmp     al, 8
0041C376 74E6          jz $0041c35e    //   je      label1
0041C378 74F0          jz $0041c36a    //   je      label2
0041C37A C3            ret             //   je      label3
                                //  label3:

返回条件跳转指令:如何在Delphi汇编程序中使用 .align 协调短条件跳转与分支目标对齐?

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

1 回答

  • 2

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

    或者您可以使用不同的汇编程序进行汇编 . 但正如我所料,这是非常不受欢迎的because you lose access to Delphi-specific stuff like object layout for things declared outside of the asm . (感谢@Rudy的评论 . )

    您可以在Delphi汇编程序中编写一些函数,尽可能多地使用Delphi特定的东西 . 将关键循环部分写入另一个汇编程序,hexdump将其机器代码输出转储到您放在Delphi程序集中间的 db 伪指令中 .

    如果每个函数的开始至少与函数内部的任何函数一致,那么这可能正常工作,但是您可能最终浪费指令或将常量放入寄存器以供NASM部件使用,这可能比仅仅具有更糟糕的情况更长的分支 .


    只有位于.align之后的指令才能正确编码为2字节短

    这不太准确 . 第一个 je label1 看起来没问题,它在 .align 之前 .

    它看起来像 any branch that goes forward across a not-yet-evaluated .align directive leaves room for a rel32 ,汇编程序永远不会回来并修复它 . 其他情况似乎都很好:跨越 .align 的向后分支,以及不跨越 .align 的向前分支 .


    分支位移优化不是一个简单的问题,尤其是当有 .align 指令时 . 不过,这似乎是一个非常次优的实现 .

    相关:Why is the "start small" algorithm for branch displacement not optimal?了解有关汇编程序用于分支位移优化的算法的更多信息 . 即使是优秀的汇编程序也可能没有做出最佳选择,尤其是当有 .align 指令时 .

相关问题