3.6.4 跳转指令的编码

3.6.4 跳转指令的编码

虽然我们不关心机器代码格式的细节,但是理解跳转指令的目标如何编码,这对第 7 章研究链接非常重要。此外,它也能帮助理解反汇编器的输出。在汇编代码中,跳转目标用符号标号书写。汇编器,以及后来的链接器,会产生跳转目标的适当编码。跳转指令有几种不同的编码,但是最常用都是 PC 相对的(PC-relative)。也就是,它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差作为编码。这些地址偏移量可以编码为 1、2 或 4 个字节。第二种编码方法是给出“绝对”地址,用 4 个字节直接指定目标。汇编器和链接器会选择适当的跳转目的编码。

下面是一个 PC 相对寻址的例子,这个函数的汇编代码由编译文件 branch.c 产生。它包含两个跳转:第 2 行的 jmp 指令前向跳转到更高的地址,而第 7 行的 jg 指令后向跳转到较低的地址。

1      movq  %rdi, %rax
2      jmp   .L2
3  .L3:
4      sarq  %rax
5  .L2:
6      testq %rax, %rax
7      jg    .L3
8      rep; ret

汇编器产生的“.o”格式的反汇编版本如下:

1  0:  48 89 f8    mov    %rdi,%rax
2  3:  eb 03       jmp    8 <loop+0x8>
3  5:  48 d1 f8    sar    %rax
4  8:  48 85 c0    test   %rax,%rax
5  b:  7f f8       jg     5 <loop+0x5>
6  d:  f3 c3       repz retq

右边反汇编器产生的注释中,第 2 行中跳转指令的跳转目标指明为 0x8,第 5 行中跳转指令的跳转目标是 0x5(反汇编器以十六进制格式给出所有的数字)。不过,观察指令的字节编码,会看到第一条跳转指令的目标编码(在第二个字节中)为 0x03。把它加上 0x5,也就是下一条指令的地址,就得到跳转目标地址 0x8,也就是第 4 行指令的地址。

类似,第二个跳转指令的目标用单字节、补码表示编码为 0xf8(十进制 -8)。将这个数加上 0xd(十进制 13),即第 6 行指令的地址,我们得到 0x5,即第 3 行指令的地址。

这些例子说明,当执行 PC 相对寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。这种惯例可以追溯到早期的实现,当时的处理器会将更新程序计数器作为执行一条指令的第一步。

下面是链接后的程序反汇编版本:

1  4004d0:  48 89 f8    mov    %rdi,%rax
2  4004d3:  eb 03       jmp    4004d8 <loop+0x8>
3  4004d5:  48 d1 f8    sar    %rax
4  4004d8:  48 85 c0    test   %rax,%rax
5  4004db:  7f f8       jg     4004d5 <loop+0x5>
6  4004dd:  f3 c3       repz retq

这些指令被重定位到不同的地址,但是第 2 行和第 5 行中跳转目标的编码并没有变。通过使用与 PC 相对的跳转目标编码,指令编码很简洁(只需要 2 个字节),而且目标代码可以不做改变就移到内存中不同的位置。

旁注 指令 reprepz 有什么用

本节开始的汇编代码的第 8 行包含指令组合 rep; ret。它们在反汇编代码中(第 6 行)对应于 repz retq。可以推测出 repzrep 的同义名,而 retqret 的同义名。

查阅 Intel 和 AMD 有关 rep 的文档,我们发现它通常用来实现重复的字符串操作 [3, 51]。在这里用它似乎很不合适。这个问题的答案可以在 AMD 给编译器编写者的指导意见书 [1] 中找到。他们建议用 rep 后面跟 ret 的组合来避免使 ret 指令成为条件跳转指令的目标。如果没有 rep 指令,当分支不跳转时,jg 指令(汇编代码的第 7 行)会继续到 ret 指令。根据 AMD 的说法,当 ret 指令通过跳转指令到达时,处理器不能正确预测 ret 指令的目的。这里的 rep 指令就是作为一种空操作,因此作为跳转目的插入它,除了能使代码在 AMD 上运行得更快之外,不会改变代码的其他行为。在本书后面其他代码中再遇到 reprepz 时,我们可以很放心地无视它们。

练习题 3.15 在下面这些反汇编二进制代码节选中,有些信息被 X 代替了。回答下列关于这些指令的问题。

A. 下面 je 指令的目标是什么?(在此,你不需要知道任何有关 callq 指令的信息。)

4003fa: 74 02       je    XXXXXX
4003fc: ff d0       callq *%rax

B. 下面 je 指令的目标是什么?

40042f: 74 f4       je    XXXXXX
400431: 5d          pop   %rbp

C. japop 指令的地址是多少?

XXXXXX: 77 02       ja    400547
XXXXXX: 5d          pop   %rbp

D. 在下面的代码中,跳转目标的编码是 PC 相对的,且是一个 4 字节补码数。字节按照从最低位到最高位的顺序列出,反映出 x86-64 的小端法字节顺序。跳转目标的地址是什么?

4005e8: e9 73 ff ff ff    jmpq XXXXXXX
4005ed: 90                nop

跳转指令提供了一种实现条件执行(if)和几种不同循环结构的方式。