如何简化将数据地址粘贴到机器代码中的直接字段的算法?
How to simplify algorithm of pasting addresses of data to immediate fields in machine code?
在开发我的汇编程序时我遇到了一个问题。在汇编语言中,我们可以定义数据值(例如 msg db 'Hi'
)并将该数据的地址粘贴到任何地方,甚至可以在该数据之上。然而,当汇编代码时,汇编程序不知道数据的地址,直到没有处理那些数据的行。
当然,汇编程序可以记住我们使用定义数据地址的机器代码地址,并在处理代码后将记住的地址中的值替换为我们数据的地址。但是,如果我们在 2 字节地址(例如 01 F1)中定义数据,汇编程序会将记住的地址中的 1 个字节替换为 2 个字节的地址(01 F1),因此立即字段大小将更改(imm8 -> imm16)和汇编程序应在同一地址重写指令(更改操作码中的位 w 和 s,并可能设置前缀 0x66)。如果汇编程序将设置前缀 0x66 并且我们的数据定义在此指令之后,则应重写立即字段字节(递增地址值)。
算法说明:
以下代码:
mov dh, 09h
add dx, msg
;...
msg db 'Hello$'
将按照以下原则组装:
- 正在准备代码:
Comment : |===> Remember address of this byte (0x0004)
Comment : | ADD DX,MSG |
Address : 0000 0001 |0002 0003 0004| ... 01F1 01F2 01F3 01F4 01F5 01F6
Code : B4 09 | 83 C2 00 | ... 48 65 6C 6C 6F 24
Comment : ---------------- H e l l o $
- 在记住的地址中重写代码:
Comment : |=============|-This address (msg)
Comment : | ADD DX,01F1 | v
Address : 0000 0001 |0002 0003 0004 0005| ... 01F2 01F3 01F4 01F5 01F6 01F7
Code : B4 09 | 83 C2 F1 01 | ... 48 65 6C 6C 6F 24
Comment : --------------------- H e l l o $
- 重写指令的操作码
83h -> 81h
(10000011b -> 10000001b
:位s=0
):
Comment : |=============|-This address (msg)
Comment : | ADD DX,01F1 | v
Address : 0000 0001 |0002 0003 0004 0005| ... 01F2 01F3 01F4 01F5 01F6 01F7
Code : B4 09 | 81 C2 F1 01 | ... 48 65 6C 6C 6F 24
Comment : --------------------- H e l l o $
- 将数据的新地址写入立即字段(
0x01F2
):
Comment : |=============|-This address (msg)
Comment : | ADD DX,01F2 | v
Address : 0000 0001 |0002 0003 0004 0005| ... 01F2 01F3 01F4 01F5 01F6 01F7
Code : B4 09 | 81 C2 F2 01 | ... 48 65 6C 6C 6F 24
Comment : --------------------- H e l l o $
我觉得这个算法很难。是否可以简化它?
如果汇编器没有发出平面二进制文件(即也是 linker),汇编器必须假设符号地址可能是 2 个字节,因为最终的绝对地址不会是直到 link 时间,汇编程序完成后才知道。 (所以它将只留下 space 一个 2 字节地址和一个重定位供 linker 填写)。
但是如果你直接组装成一个扁平的二进制文件并想做这个优化,大概你会用开始小的算法把它当作分支位移来对待,然后做多遍优化直到一切fits. 实际上,您会将此作为查看 jmp/jcc rel8
与 jmp/jcc rel16
的相同传球的一部分。 (Why is the "start small" algorithm for branch displacement not optimal? - 如果你没有像 align 8
这样的东西,它是最佳的,否则在某些极端情况下它可以但不是最佳的。)
这些优化过程只是循环表示代码的内部数据结构,而不是在每一步实际编写最终的机器代码。不需要计算或查找实际操作码和 ModRM 编码,直到最后一次优化通过之后。
你只需要你的优化器知道指令大小的规则,例如add reg, imm8
是 3 个字节,add reg, imm16
是 4 个字节(除了 AX,其中 add ax, imm16
有一个 3 字节的特殊编码,与 add ax, imm8
相同,所以添加到 AX 不会'根本不需要成为多遍优化的一部分,它可以在我们知道所有符号地址后到达它时选择一种编码。)
请注意,使用地址作为 mov
的立即数更为常见,这根本不允许窄立即数(mov reg, imm16
始终为 3 个字节)。但是这种优化在寻址模式中也与 disp8 和 disp16 相关,例如对于 xor cl, [di + msg]
可以将 reg+disp8 用于小地址,因此值得进行此优化。
因此,您的优化器传递会再次知道 [di + disp8]
在 ModRM 之后占用 1 个字节,[di + disp16]
额外占用 2 个字节。
而[msg]
总是在ModRM之后占用2个字节,没有[disp8]
编码。如果 asm 源想要利用 disp8 来处理小地址,它需要有一个置零寄存器。
当然,一个简单的或一次通过的汇编程序总是可以假设地址是 16 位,并相应地对机器代码的其他部分进行编码,只有在看到未解析的符号时才返回填充数字地址。 (或者在最后发出搬迁信息,让 link 人来做。)
在开发我的汇编程序时我遇到了一个问题。在汇编语言中,我们可以定义数据值(例如 msg db 'Hi'
)并将该数据的地址粘贴到任何地方,甚至可以在该数据之上。然而,当汇编代码时,汇编程序不知道数据的地址,直到没有处理那些数据的行。
当然,汇编程序可以记住我们使用定义数据地址的机器代码地址,并在处理代码后将记住的地址中的值替换为我们数据的地址。但是,如果我们在 2 字节地址(例如 01 F1)中定义数据,汇编程序会将记住的地址中的 1 个字节替换为 2 个字节的地址(01 F1),因此立即字段大小将更改(imm8 -> imm16)和汇编程序应在同一地址重写指令(更改操作码中的位 w 和 s,并可能设置前缀 0x66)。如果汇编程序将设置前缀 0x66 并且我们的数据定义在此指令之后,则应重写立即字段字节(递增地址值)。
算法说明:
以下代码:
mov dh, 09h
add dx, msg
;...
msg db 'Hello$'
将按照以下原则组装:
- 正在准备代码:
Comment : |===> Remember address of this byte (0x0004)
Comment : | ADD DX,MSG |
Address : 0000 0001 |0002 0003 0004| ... 01F1 01F2 01F3 01F4 01F5 01F6
Code : B4 09 | 83 C2 00 | ... 48 65 6C 6C 6F 24
Comment : ---------------- H e l l o $
- 在记住的地址中重写代码:
Comment : |=============|-This address (msg)
Comment : | ADD DX,01F1 | v
Address : 0000 0001 |0002 0003 0004 0005| ... 01F2 01F3 01F4 01F5 01F6 01F7
Code : B4 09 | 83 C2 F1 01 | ... 48 65 6C 6C 6F 24
Comment : --------------------- H e l l o $
- 重写指令的操作码
83h -> 81h
(10000011b -> 10000001b
:位s=0
):
Comment : |=============|-This address (msg)
Comment : | ADD DX,01F1 | v
Address : 0000 0001 |0002 0003 0004 0005| ... 01F2 01F3 01F4 01F5 01F6 01F7
Code : B4 09 | 81 C2 F1 01 | ... 48 65 6C 6C 6F 24
Comment : --------------------- H e l l o $
- 将数据的新地址写入立即字段(
0x01F2
):
Comment : |=============|-This address (msg)
Comment : | ADD DX,01F2 | v
Address : 0000 0001 |0002 0003 0004 0005| ... 01F2 01F3 01F4 01F5 01F6 01F7
Code : B4 09 | 81 C2 F2 01 | ... 48 65 6C 6C 6F 24
Comment : --------------------- H e l l o $
我觉得这个算法很难。是否可以简化它?
如果汇编器没有发出平面二进制文件(即也是 linker),汇编器必须假设符号地址可能是 2 个字节,因为最终的绝对地址不会是直到 link 时间,汇编程序完成后才知道。 (所以它将只留下 space 一个 2 字节地址和一个重定位供 linker 填写)。
但是如果你直接组装成一个扁平的二进制文件并想做这个优化,大概你会用开始小的算法把它当作分支位移来对待,然后做多遍优化直到一切fits. 实际上,您会将此作为查看 jmp/jcc rel8
与 jmp/jcc rel16
的相同传球的一部分。 (Why is the "start small" algorithm for branch displacement not optimal? - 如果你没有像 align 8
这样的东西,它是最佳的,否则在某些极端情况下它可以但不是最佳的。)
这些优化过程只是循环表示代码的内部数据结构,而不是在每一步实际编写最终的机器代码。不需要计算或查找实际操作码和 ModRM 编码,直到最后一次优化通过之后。
你只需要你的优化器知道指令大小的规则,例如add reg, imm8
是 3 个字节,add reg, imm16
是 4 个字节(除了 AX,其中 add ax, imm16
有一个 3 字节的特殊编码,与 add ax, imm8
相同,所以添加到 AX 不会'根本不需要成为多遍优化的一部分,它可以在我们知道所有符号地址后到达它时选择一种编码。)
请注意,使用地址作为 mov
的立即数更为常见,这根本不允许窄立即数(mov reg, imm16
始终为 3 个字节)。但是这种优化在寻址模式中也与 disp8 和 disp16 相关,例如对于 xor cl, [di + msg]
可以将 reg+disp8 用于小地址,因此值得进行此优化。
因此,您的优化器传递会再次知道 [di + disp8]
在 ModRM 之后占用 1 个字节,[di + disp16]
额外占用 2 个字节。
而[msg]
总是在ModRM之后占用2个字节,没有[disp8]
编码。如果 asm 源想要利用 disp8 来处理小地址,它需要有一个置零寄存器。
当然,一个简单的或一次通过的汇编程序总是可以假设地址是 16 位,并相应地对机器代码的其他部分进行编码,只有在看到未解析的符号时才返回填充数字地址。 (或者在最后发出搬迁信息,让 link 人来做。)