如何从 switch 语句中获取跳转 table 的案例编号
how to get case number for jump table from a switch statement
我正在学习这本教科书,Randal E. Bryant、David R. O'Hallaron - 计算机系统。程序员的视角 [第 3 版](2016 年,Pearson)
我一直在想作者是如何计算出开关的案例编号 table,如下所示~
我不确定的是他们如何获得案例编号,例如 Case A
是 Case 5
: 以及 Case 2/7
是 Case C
和 Case D
,依此类推本例中的其他情况
感谢任何帮助谢谢!!
首先,您的书有一个错误 - 它说“a
in %rsi
,b
in %rdi
" - 但这是 not the standard x64 calling convention 并且与程序集的其余部分不一致。
这本书的意思:
%rdi
-> a
、%rsi
-> b
、%rdx
-> c
和 %rcx
-> dest
继续,让我们了解会发生什么:
默认代码块
前两个操作码是:
cmpq , %rdi
ja .L2
ja
如果在上方则跳转,即如果 a > 7
则转到 .L2
- 这是程序集的末尾。我们可以推断这是 default
代码块(它立即继续到函数的末尾)——在 .L2
下我们有:
movq %rsi, %rdi
movq %rdi, %(rcx) ; this corresponds to *dest = val in C
所以我们可以得出结论,在这种情况下,%(rcx)
获得了 %rsi
的值 - 换句话说,在默认代码块中,val = b
.
切换代码块
如果我们上面的第一个ja
没有执行,那么我们jmp *.L4(,%rdi,8)
。由于 a
不大于 7,我们有八种可能性 - 我们可以在 .L4
table 中看到:
- 如果
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
。然后我们跳转到函数的末尾 - 我们可以得出结论 case 0
代码块中的 val = c + 112
。
.L7,或案例 5
此块运行 leaq (%rdx, %rsi), %rdi
,它将 %rdi
设置为 %rdx + %rsi
(即 c + b
)- 然后调用 salq , %rdi
,它只是将此移动剩下 2 位的值 - 总值 (c + b) << 2
。然后我们跳转到函数的末尾 - 我们可以得出结论 case 5
代码块中的 val = (c + b) << 2
。
.L6,或案例 4
这里我们直接跳到了函数的末尾,只是调用了 movq %rdi, (%rcx)
操作码——这实际上等同于设置 *dest = a
。我们可以得出结论,在这种情况下,val = a
.
.L7,或案例 5
此块运行 xorq , %rsi
- 相当于 b ^= 15
。然后它运行 movq %rsi, %rdx
- 将 c
设置为此值。然后我们直接进入上面描述的 .L3
- 它设置 val = c + 112
。我们可以得出结论,.L7
是我们的 fall-through switch case。
一般来说,反转 switch cases 可以非常简单 - 它主要涉及理解跳转 table 如何对应于比较寄存器中的不同值(注意这里 a
的几个可能值是如何映射的到 table) 中的相同跳转 - 并了解不同切换情况之间的失败。
我正在学习这本教科书,Randal E. Bryant、David R. O'Hallaron - 计算机系统。程序员的视角 [第 3 版](2016 年,Pearson)
我一直在想作者是如何计算出开关的案例编号 table,如下所示~
我不确定的是他们如何获得案例编号,例如 Case A
是 Case 5
: 以及 Case 2/7
是 Case C
和 Case D
,依此类推本例中的其他情况
感谢任何帮助谢谢!!
首先,您的书有一个错误 - 它说“a
in %rsi
,b
in %rdi
" - 但这是 not the standard x64 calling convention 并且与程序集的其余部分不一致。
这本书的意思:
%rdi
->a
、%rsi
->b
、%rdx
->c
和%rcx
->dest
继续,让我们了解会发生什么:
默认代码块
前两个操作码是:
cmpq , %rdi
ja .L2
ja
如果在上方则跳转,即如果 a > 7
则转到 .L2
- 这是程序集的末尾。我们可以推断这是 default
代码块(它立即继续到函数的末尾)——在 .L2
下我们有:
movq %rsi, %rdi
movq %rdi, %(rcx) ; this corresponds to *dest = val in C
所以我们可以得出结论,在这种情况下,%(rcx)
获得了 %rsi
的值 - 换句话说,在默认代码块中,val = b
.
切换代码块
如果我们上面的第一个ja
没有执行,那么我们jmp *.L4(,%rdi,8)
。由于 a
不大于 7,我们有八种可能性 - 我们可以在 .L4
table 中看到:
- 如果
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
。然后我们跳转到函数的末尾 - 我们可以得出结论 case 0
代码块中的 val = c + 112
。
.L7,或案例 5
此块运行 leaq (%rdx, %rsi), %rdi
,它将 %rdi
设置为 %rdx + %rsi
(即 c + b
)- 然后调用 salq , %rdi
,它只是将此移动剩下 2 位的值 - 总值 (c + b) << 2
。然后我们跳转到函数的末尾 - 我们可以得出结论 case 5
代码块中的 val = (c + b) << 2
。
.L6,或案例 4
这里我们直接跳到了函数的末尾,只是调用了 movq %rdi, (%rcx)
操作码——这实际上等同于设置 *dest = a
。我们可以得出结论,在这种情况下,val = a
.
.L7,或案例 5
此块运行 xorq , %rsi
- 相当于 b ^= 15
。然后它运行 movq %rsi, %rdx
- 将 c
设置为此值。然后我们直接进入上面描述的 .L3
- 它设置 val = c + 112
。我们可以得出结论,.L7
是我们的 fall-through switch case。
一般来说,反转 switch cases 可以非常简单 - 它主要涉及理解跳转 table 如何对应于比较寄存器中的不同值(注意这里 a
的几个可能值是如何映射的到 table) 中的相同跳转 - 并了解不同切换情况之间的失败。