我最近在 codegolf 的“使用 x86/x64 机器码打高尔夫球的技巧”中添加了一个关于 skipping instructions 的技巧。您会发现这些是跳入先前指令的一部分的有意应用。而且不仅仅是为了混淆。这是该答案的全文:
跳过说明
跳过指令是与一个或多个后续操作码组合的操作码片段。后续的操作码可以用于与前置跳过指令不同的入口点。使用跳过指令而不是无条件短跳转可以节省代码空间,速度更快,并设置诸如NC(无进位)之类的附带状态。
我的示例都是针对 16 位实/虚拟 86 模式的,但其中很多技术可以类似地用于 16 位保护模式或 32 位或 64 位模式。
引用from my ACEGALS guide:
11:跳过指令
常量 __TEST_IMM8、__TEST_IMM16 和 __TEST_OFS16_IMM8 被定义为这些指令的相应字节串。它们可用于跳过适合以下 1、2 或 3 个字节的后续指令。但是,请注意,它们会修改标志寄存器,包括始终设置 NC。 16 位偏移加 16 位立即测试指令不包括在这些用途中,因为它可能访问段中偏移 0FFFFh 处的字。此外,所提供的 __TEST_OFS16_IMM8 应仅在 86M 中使用,以避免访问超出段限制的数据。在使用这些常量之一的 db 指令之后,括号中应列出哪些指令被跳过。
86位模式定义in lmacros1.mac 323cc150061e (2021-08-29 21:45:54 +0200):
%define __TEST_IMM8 0A8h ; changes flags, NC
%define __TEST_IMM16 0A9h ; changes flags, NC
; Longer NOPs require two bytes, like a short jump does.
; However they execute faster than unconditional jumps.
; This one reads random data in the stack segment.
; (Search for better ones.)
%define __TEST_OFS16_IMM8 0F6h,86h ; changes flags, NC
16 位模式下的0F6h,86h 操作码是test byte [bp + disp16], imm8 指令。我相信我实际上并没有在任何地方使用这个。 (实际上,堆栈内存访问实际上可能比无条件短跳转慢。)
0A8h 是test al, imm8 在任何模式下的操作码。 0A9h 操作码在 32 位和 64 位模式下更改为 test eax, imm32 形式的指令。
两个用例in ldosboot boot32.asm 07f4ba0ef8cd (2021-09-10 22:45:32 +0200):
首先,为一个公共函数链接两个不同的入口点,这两个入口点都需要初始化一个字节大小的寄存器。 mov al, X 指令每条占用 2 个字节,因此 __TEST_IMM16 可用于跳过此类指令。 (如果有两个以上的入口点,则可以重复此模式。)
error_fsiboot:
mov al,'I'
db __TEST_IMM16 ; (skip mov)
read_sector.err:
mov al, 'R' ; Disk 'R'ead error
error:
其次,某个入口点需要两个字节的额外拆卸,但可以与后续代码部分的失败案例共享。
mov bx, [VAR(para_per_sector)]
sub word [VAR(paras_left)], bx
jbe @F ; read enough -->
loop @BB
pop bx
pop cx
call clust_next
jnc next_load_cluster
inc ax
inc ax
test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh,
; clear in 0, 1, and 0FFF_FFF7h
jz fsiboot_error_badchain
db __TEST_IMM16
@@:
pop bx
pop cx
call check_enough
jmp near word [VAR(fsiboot_table.success)]
这是一个用例 in inicomp lz4.asm 4d568330924c (2021-09-03 16:59:42 +0200),我们依赖于 test al, X 指令清除进位标志:
.success:
db __TEST_IMM8 ; (NC)
.error:
stc
retn
最后,在DOSLFN Version 0.41c (11/2012) 中有一个非常相似的跳过指令用法。他们使用的是mov cx, imm16 而不是test ax, imm16,这对状态标志没有影响,但会破坏cx 寄存器。 (操作码 0B9h 在非 16 位模式下为 mov ecx, imm32,写入完整的 ecx 或 rcx 寄存器。)
;THROW-Geschichten... [english: THROW stories...]
SetErr18:
mov al,18
db 0B9h ;mov cx,nnnn
SetErr5:
mov al,5
db 0B9h ;mov cx,nnnn
SetErr3:
mov al,3
db 0B9h ;mov cx,nnnn
SetErr2:
mov al,2
SetError: