首先,你的书有一个错误——它说“ a
in %rsi
, b
in %rdi
”——但这不是标准的 x64 调用约定,它与程序集的其余部分不一致。
这本书的意思:
%rdi
-> a
、%rsi
-> b
、%rdx
->c
和%rcx
->dest
继续,让我们了解会发生什么:
默认代码块
前两个操作码是:
cmpq $7, %rdi
ja .L2
ja
如果高于则跳转,即如果a > 7
然后转到.L2
- 这是在程序集的末尾。我们可以推断这是default
代码块(它立即继续到函数的末尾) - 在下面.L2
我们有:
movq %rsi, %rdi
movq %rdi, %(rcx) ; this corresponds to *dest = val in C
因此我们可以得出结论,在这种情况下, %(rcx)
gets%rsi
的值 - 换句话说,在默认代码块中,val = b
.
切换代码块
如果我们ja
上面的第一个没有执行,那么我们jmp *.L4(,%rdi,8)
. 由于a
不高于 7,我们有八种可能性 - 我们可以在.L4
表中看到:
- 如果
a == 0
然后我们跳转到.L3
- 如果
a == 1
, a == 3
, 或a == 6
, 我们跳转到.L2
(我们的默认代码块,如上所述)
- 如果
a == 2
或a == 7
我们跳转到.L5
- 如果
a == 4
我们跳到.L6
- 如果
a == 5
我们跳到.L7
.L3,或案例 0
这个块运行leaq 112(%rdx), %rdi
,它只是具有设置%rdi
为%rdx + 112
- 这是的效果c + 112
。然后我们跳到函数的末尾——我们可以val = c + 112
在case 0
代码块中得出结论。
.L7,或案例 5
这个块运行leaq (%rdx, %rsi), %rdi
,它设置%rdi
为%rdx + %rsi
(它是c + b
)-然后调用salq $2, %rdi
,它只是将此值左移 2 位-总值为(c + b) << 2
. 然后我们跳到函数的末尾——我们可以val = (c + b) << 2
在case 5
代码块中得出结论。
.L6,或案例 4
在这里,我们立即跳到了函数的末尾,只调用了movq %rdi, (%rcx)
操作码——这实际上等同于设置*dest = a
。我们可以得出结论,在这种情况下,val = a
。
.L7,或案例 5
此块运行xorq $15, %rsi
- 相当于b ^= 15
. 然后它运行movq %rsi, %rdx
- 设置c
为这个值。然后我们继续直接进入.L3
上面描述的 - 设置val = c + 112
。我们可以得出结论,这.L7
是我们的故障开关盒。
一般来说,反转开关情况可能非常简单——它主要涉及理解跳转表如何对应于比较寄存器中的不同值(注意这里有几个可能的值如何a
映射到表中的同一个跳转)——以及理解下降——不同开关盒之间的通道。