题目
选题三
对于以下c程序:
#include <stdio.h>
int main()
{
int i=0;
int j=0;
switch(i)
{
case 1:
j+=1;
break;
case 2:
j+=2;
break;
case 3:
j+=3;
break;
case 4:
j+=4;
break;
case 5:
j+=5;
case 6:
j+=70;
break;
default:
j+=5;
break;
}
return 0;
}
(1)将其编译成汇编代码,找到跳转表,并分析汇编代码是如何通过跳转表来完成switch功能的;
(2)将分支条件调整为case 6,case 2,case 5,case 3,case 4,case 1(即交换一下分支条件顺序),观察跳转表的变化情况。
(3)将分支条件调整为case 5, case 3, case 2, case1,或是调整为case 138,case 106, case 2, case 9, case 68后,汇编后的代码中不包括跳转表,而是采用cmpl,je, jmp等指令来实现switch功能,请再找出几组能生成跳转表与不生成跳转表的分支条件组合,并分析编译器在哪些情况下更有可能采用跳转表的方式来实现switch功能。同时考察不同的编译器是否有不同的行为。
第一问:跳转表
编译:gcc switch.c -S -o switch.s -O0
汇编如下,地址跳转部分有注释:
main:
.LFB0:
.cfi_startproc
endbr32
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl $0, -4(%ebp) ;i=0
movl $0, -8(%ebp) ;j=0
cmpl $6, -4(%ebp) ;if i>6, jmp default
ja .L2
movl -4(%ebp), %edx ;edx=i=0
sall $2, %edx ;edx=4*i=0
movl .L4@GOTOFF(%edx,%eax), %edx ;edx=table[4*i]
addl %edx, %eax
notrack jmp *%eax ;jmp table[edx]
.section .rodata
.align 4
.align 4
.L4:
.long .L2@GOTOFF ;default
.long .L9@GOTOFF ;case 1
.long .L8@GOTOFF ;case 2
.long .L7@GOTOFF ;case 3
.long .L6@GOTOFF ;case 4
.long .L5@GOTOFF ;case 5
.long .L3@GOTOFF ;case 6
.text
.L9:
addl $1, -8(%ebp)
jmp .L10
.L8:
addl $2, -8(%ebp)
jmp .L10
.L7:
addl $3, -8(%ebp)
jmp .L10
.L6:
addl $4, -8(%ebp)
jmp .L10
.L5:
addl $5, -8(%ebp)
.L3:
addl $6, -8(%ebp)
jmp .L10
.L2:
addl $70, -8(%ebp)
nop
.L10:
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
第二问:交换分支顺序
将分支条件调整为case 6,case 2,case 5,case 3,case 4,case 1(即交换一下分支条件顺序),汇编结果不变
第三问:是否生成跳转表
总结
The compiler may choose cmp/je when the numbers are far apart; if the numbers are close together, the compiler will typically use a jumptable.
1、密集分布的case分支,采用跳转表
2、稀疏分布的case分支,采用直接跳转;若总数量较多,或者间隔较大,用二叉树搜索式跳转
参考
C++逆向学习三步走 :1.6、switch-case识别技巧提高
笔记:
switch-case语句(Release版本)
<1>switch里面的值如果是常量,则编译器直接裁掉无用分支,保留所要执行的分支。
<2>case如果case后面语句有规律的变化则编译器会对switch中的值进行减法,并依次与case后面值进行比较。
<3>case中的语句块内容如果一样,则编译器并不会将其合并。
<4>case后面的值如果没有规律且跨度不大,跨度值有一个上限,在此范围内则编译器会使用jump table处理分支跳转。
<5>如果case后面的值差距在255之间则使用间接表,即稀疏矩阵进行查找。
<6>如果case后面的值差距大于255,则用平衡二叉树,时间复杂度为2^k - 1(k为二叉树深度)。
https://stackoverflow.com/questions/17061967/c-switch-and-jump-tables
一个方便查看不同编译器汇编代码的网站:https://godbolt.org/