在 Delphi 汇编程序中,如何使用 `.align` 协调短条件跳转与分支目标对齐?
How can I reconcile short conditional jumps with branch target alignments with `.align` in Delphi assembler?
如何在 Delphi 汇编程序中协调短条件跳转与分支目标对齐?
我正在使用 Delphi 版本 10.2 Tokyo,用于 32 位和 64 位汇编,完全使用汇编编写一些函数。
如果我不使用 .align
,编译器会正确编码 short
条件跳转指令(2 字节指令,由 1 字节操作码 074h
和 1-字节相对偏移量 -+ 高达 07Fh)。但是,如果我曾经放过一个 .align
,甚至小到 .align 4
——所有位于 .align 之前并且目标位于 .align
之后的条件跳转指令——在这个如果所有这些指令都变成 6 字节指令,而不是它们应该的 2 字节指令。只有位于 .align 之后的指令仍正确编码为 2 字节 short
.
Delphi 汇编程序不接受“短”前缀。
如何在 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, // mov al, 1
0041C356 3C02 cmp al, // cmp al, 2
0041C358 740C jz [=12=]41c366 // je label1
0041C35A 0F841C000000 jz [=12=]41c37c // je label2
0041C360 0F8426000000 jz [=12=]41c38c // je label3
0041C366 B003 mov al, //label1: mov al, 3
0041C368 3C04 cmp al, // cmp al, 4
0041C36A 74FA jz [=12=]41c366 // je label1
0041C36C 0F840A000000 jz [=12=]41c37c // je label2
0041C372 0F8414000000 jz [=12=]41c38c // je label3
0041C378 B005 mov al, // 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, //label2: cmp al, 6
0041C37E 74E6 jz [=12=]41c366 // je label1
0041C380 74FA jz [=12=]41c37c // je label2
0041C382 7408 jz [=12=]41c38c // je label3
0041C384 B007 mov al, // mov al, 7
0041C386 3C08 cmp al, // cmp al, 8
0041C388 74DC jz [=12=]41c366 // je label1
0041C38A 74F0 jz [=12=]41c37c // je label2
0041C38C C3 ret // label3:
但是如果我删除 .align
- 所有的指令都有正确的大小 - 就像以前一样只有 2 个字节:
0041C354 B001 mov al, // mov al, 1
0041C356 3C02 cmp al, // cmp al, 2
0041C358 7404 jz [=13=]41c35e // je label1
0041C35A 740E jz [=13=]41c36a // je label2
0041C35C 741C jz [=13=]41c37a // je label3
0041C35E B003 mov al, //label1: mov al, 3
0041C360 3C04 cmp al, // cmp al, 4
0041C362 74FA jz [=13=]41c35e // je label1
0041C364 7404 jz [=13=]41c36a // je label2
0041C366 7412 jz [=13=]41c37a // je label3
0041C368 B005 mov al, // mov al, 5
0041C36A 3C06 cmp al, //.align 4 label2:cmp al, 6
0041C36C 74F0 jz [=13=]41c35e // je label1
0041C36E 74FA jz [=13=]41c36a // je label2
0041C370 7408 jz [=13=]41c37a // je label3
0041C372 B007 mov al, // mov al, 7
0041C374 3C08 cmp al, // cmp al, 8
0041C376 74E6 jz [=13=]41c35e // je label1
0041C378 74F0 jz [=13=]41c36a // je label2
0041C37A C3 ret // je label3
// label3:
返回条件跳转指令:如何在 Delphi 汇编程序中使用 .align
协调短条件跳转与分支目标对齐?
我承认在像 SkyLake 和更高版本这样的处理器上对齐分支目标的好处很小,我知道我可以避免使用 .align
- 它也会节省代码大小。但我想知道如何使用 Delphi 汇编器生成带有 align
的短跳转。这个问题在 32 位目标中也存在,不仅在 64 位目标中。
除非你的 assembler 有一个选项来做更好的分支位移优化(这可能需要重复通过),否则你可能不走运。 (当然你可以自己手动完成所有对齐,但每次更改任何内容都必须重新完成。)
或者您可以使用与 assemble 不同的 assembler。但正如我所料,这是非常不受欢迎的 。 (感谢@Rudy 的评论。)
您可以在 Delphi assembler 中编写一些函数,并尽可能多地执行 Delphi 特定的内容。在另一个 assembler 中编写关键循环部分,hexdump 将其机器代码输出转储到您放在 Delphi 程序集中间的 db
伪指令中。
如果每个函数的开头至少与函数内部的任何内容一样对齐,这可能会工作正常,但您可能最终会浪费指令或将常量放入寄存器以供 NASM 部分使用,这可能比只有更长的分支更糟糕。
Only the instructions that are located after the .align remain correctly encoded as 2-byte short
这不太准确。第一个 je label1
看起来不错,它在 .align
.
之前
看起来 任何向前跨越尚未评估的 .align
指令的分支都为 rel32
和 assembler 永远不会回来修复它。其他情况似乎都很好:向后分支跨越 .align
,向前分支不跨越 .align
.
分支位移优化不是一个简单的问题,尤其是当有 .align
个指令时。不过,这似乎是一个真正次优的实现。
相关:Why is the "start small" algorithm for branch displacement not optimal? 了解有关 assemblers 用于分支置换优化的算法的更多信息。即使是好的 assemblers 也可能不会做出最佳选择,尤其是当有 .align
指令时。
如何在 Delphi 汇编程序中协调短条件跳转与分支目标对齐?
我正在使用 Delphi 版本 10.2 Tokyo,用于 32 位和 64 位汇编,完全使用汇编编写一些函数。
如果我不使用 .align
,编译器会正确编码 short
条件跳转指令(2 字节指令,由 1 字节操作码 074h
和 1-字节相对偏移量 -+ 高达 07Fh)。但是,如果我曾经放过一个 .align
,甚至小到 .align 4
——所有位于 .align 之前并且目标位于 .align
之后的条件跳转指令——在这个如果所有这些指令都变成 6 字节指令,而不是它们应该的 2 字节指令。只有位于 .align 之后的指令仍正确编码为 2 字节 short
.
Delphi 汇编程序不接受“短”前缀。
如何在 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, // mov al, 1
0041C356 3C02 cmp al, // cmp al, 2
0041C358 740C jz [=12=]41c366 // je label1
0041C35A 0F841C000000 jz [=12=]41c37c // je label2
0041C360 0F8426000000 jz [=12=]41c38c // je label3
0041C366 B003 mov al, //label1: mov al, 3
0041C368 3C04 cmp al, // cmp al, 4
0041C36A 74FA jz [=12=]41c366 // je label1
0041C36C 0F840A000000 jz [=12=]41c37c // je label2
0041C372 0F8414000000 jz [=12=]41c38c // je label3
0041C378 B005 mov al, // 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, //label2: cmp al, 6
0041C37E 74E6 jz [=12=]41c366 // je label1
0041C380 74FA jz [=12=]41c37c // je label2
0041C382 7408 jz [=12=]41c38c // je label3
0041C384 B007 mov al, // mov al, 7
0041C386 3C08 cmp al, // cmp al, 8
0041C388 74DC jz [=12=]41c366 // je label1
0041C38A 74F0 jz [=12=]41c37c // je label2
0041C38C C3 ret // label3:
但是如果我删除 .align
- 所有的指令都有正确的大小 - 就像以前一样只有 2 个字节:
0041C354 B001 mov al, // mov al, 1
0041C356 3C02 cmp al, // cmp al, 2
0041C358 7404 jz [=13=]41c35e // je label1
0041C35A 740E jz [=13=]41c36a // je label2
0041C35C 741C jz [=13=]41c37a // je label3
0041C35E B003 mov al, //label1: mov al, 3
0041C360 3C04 cmp al, // cmp al, 4
0041C362 74FA jz [=13=]41c35e // je label1
0041C364 7404 jz [=13=]41c36a // je label2
0041C366 7412 jz [=13=]41c37a // je label3
0041C368 B005 mov al, // mov al, 5
0041C36A 3C06 cmp al, //.align 4 label2:cmp al, 6
0041C36C 74F0 jz [=13=]41c35e // je label1
0041C36E 74FA jz [=13=]41c36a // je label2
0041C370 7408 jz [=13=]41c37a // je label3
0041C372 B007 mov al, // mov al, 7
0041C374 3C08 cmp al, // cmp al, 8
0041C376 74E6 jz [=13=]41c35e // je label1
0041C378 74F0 jz [=13=]41c36a // je label2
0041C37A C3 ret // je label3
// label3:
返回条件跳转指令:如何在 Delphi 汇编程序中使用 .align
协调短条件跳转与分支目标对齐?
我承认在像 SkyLake 和更高版本这样的处理器上对齐分支目标的好处很小,我知道我可以避免使用 .align
- 它也会节省代码大小。但我想知道如何使用 Delphi 汇编器生成带有 align
的短跳转。这个问题在 32 位目标中也存在,不仅在 64 位目标中。
除非你的 assembler 有一个选项来做更好的分支位移优化(这可能需要重复通过),否则你可能不走运。 (当然你可以自己手动完成所有对齐,但每次更改任何内容都必须重新完成。)
或者您可以使用与 assemble 不同的 assembler。但正如我所料,这是非常不受欢迎的
您可以在 Delphi assembler 中编写一些函数,并尽可能多地执行 Delphi 特定的内容。在另一个 assembler 中编写关键循环部分,hexdump 将其机器代码输出转储到您放在 Delphi 程序集中间的 db
伪指令中。
如果每个函数的开头至少与函数内部的任何内容一样对齐,这可能会工作正常,但您可能最终会浪费指令或将常量放入寄存器以供 NASM 部分使用,这可能比只有更长的分支更糟糕。
Only the instructions that are located after the .align remain correctly encoded as 2-byte short
这不太准确。第一个 je label1
看起来不错,它在 .align
.
看起来 任何向前跨越尚未评估的 .align
指令的分支都为 rel32
和 assembler 永远不会回来修复它。其他情况似乎都很好:向后分支跨越 .align
,向前分支不跨越 .align
.
分支位移优化不是一个简单的问题,尤其是当有 .align
个指令时。不过,这似乎是一个真正次优的实现。
相关:Why is the "start small" algorithm for branch displacement not optimal? 了解有关 assemblers 用于分支置换优化的算法的更多信息。即使是好的 assemblers 也可能不会做出最佳选择,尤其是当有 .align
指令时。