如何简化将数据地址粘贴到机器代码中的直接字段的算法?

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$'

将按照以下原则组装:

  1. 正在准备代码:
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    $
  1. 在记住的地址中重写代码:
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    $
  1. 重写指令的操作码83h -> 81h10000011b -> 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    $
  1. 将数据的新地址写入立即字段(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 rel8jmp/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 人来做。)