浅谈: switch语句汇编跳转表的解码方式
示例
设一个C函数:
void switcher(long x) {
switch(x) {
...
}
...
}
其由gcc编译后开头的一段asm如下:
;void switcher(long x)
;x in %rdi
switcher:
addq $1, %rdi
cmpq $8, %rdi
ja .L2
jmp *.L4(,%rdi,8)
...
生成的跳转表如下:
L4
.quad .L9
.quad .L5
.quad .L6
.quad .L7
.quad .L2
.quad .L7
.quad .L8
.quad .L2
.quad .L5
根据这些信息,如何确定\(\texttt{switch}\)体的内部结构?
-
确定x的最大取值:假设跳转表寻址在C伪代码表示中用来保存分支段地址的数组为jt,其下标索引由变量\(\texttt{index}\)负责。根据asm的第二行推知,\(\texttt{index = x + 1}\);根据asm第二、三行推知,若\(\texttt{index}>\texttt{8}\)则控制流直接跳到\(\texttt{default}\)分支,进而得出只有当\(\texttt{index} \leq 8\)时才会进入case判断,也即\(\texttt{x} \leq 7\)。
-
确定x的最小取值:由于数组索引由0开始,那么当x为可能的最小值时,会有\(\texttt{index} = \texttt{x}+\texttt{1}=\texttt{0}\),得出\(\texttt{x}\)最小取值为-1.
-
在跳转表每个标号地址后标注对应的数组索引。已知索引是由\(\texttt{index}\)确定的,进而在后面标注x的对应case取值。由于.L2对应的是\(\texttt{default}\)分支,则说明对应的case不存在,此时标一个*号说明该case实际上不存在。
.L4 .quad .L9 <- jt[0] index=0 -> x=-1 -> case -1 .quad .L5 <- jt[1] index=1 -> x=0 -> case 0 .quad .L6 <- jt[2] index=2 -> x=1 -> case 1 .quad .L7 <- jt[3] index=3 -> x=2 -> case 2 .quad .L2 <- jt[4] index=4 -> x=3 -> case 3* .quad .L7 <- jt[5] index=5 -> x=4 -> case 4 .quad .L8 <- jt[6] index=6 -> x=5 -> case 5 .quad .L2 <- jt[7] index=7 -> x=6 -> case 6* .quad .L5 <- jt[8] index=8 -> x=7 -> case 7 -
至此已经可以写出switch内部大致结构了,可以先用伪代码表示一下。
注意标准了*号的分支不存在,所以不写出来。另外某些case对应同一个标号地址,则将它们放到对应的标号段中:L9: case -1;break; L5: case 0 case 7; break; L6: case 1; break; L7: case 2 case 4; break; L8: case 5; break;
总结一下,以上步骤做完后,便可以定出不存在的case、存在case坐落的标号段。不过标号段的顺序现在是无法确定的。在顺序未确定之前,对应的switch源码可写为:
void switcher(long x) {
switch(x) {
case -1:break;
case 0:
case 7: break;
case 1: break;
case 2:
case 4: break;
case 5: break;
}
...
}
亦可写为:
void switcher(long x) {
switch(x) {
case 0:
case 7: break;
...
case -1:break; // 将case -1放在最后。
}
...
}
我们在上面列出了asm代码的前几行,将后面的一些信息补全的话,switch内部的顺序便可固定下来:
;void switcher(long x)
;x in %rdi
switcher:
addq $1, %rdi
cmpq $8, %rdi
ja .L2
jmp *.L4(,%rdi,8)
.section .rodata
.L6:
...
.L5:
...
.L7:
...
.L9:
...
.L8:
...
现在根据标号段的顺序,便可写出对应case的顺序了:
void switcher(long x) {
switch(x) {
// .L6
case 1: break;
// .L5
case 0:
case 7: break;
// .L7
case 2:
case 4: break;
// .L9
case -1:break;
// .L8
case 5: break;
}
...
}
值得一提的是,像case2、case4这种同标号段呢的分支顺序可以互换,因为它们对应的是同一种情况。

浙公网安备 33010602011771号