反汇编-流程控制语句-1-条件控制语句

所谓的流程控制语句在C/C++中就是,if-else switch for while语句

也就是条件语句和循环语句的合集

if-else

纯if语句

if在c语言中只能判断真或假,真就执行if里面的东西,为假就跳过

void iftest(int a)
{
00413270 push       ebp  
00413271 mov         ebp,esp  
00413273 sub         esp,40h  
00413276 push       ebx  
00413277 push       esi  
00413278 push       edi  
if (a > 0)
00413279 cmp         dword ptr [a],0  
0041327D jle         iftest+1Eh (041328Eh)  
{
printf("a>0 \n");
0041327F push       offset string "a>0\n" (0415B64h)  
00413284 call       _printf (04110AFh)  
00413289 add         esp,4  
return;
0041328C jmp         iftest+2Bh (041329Bh)  
}
printf("if语句调用失败\n");
0041328E push       offset string "if\xd3\xef\xbe\xe4\xb5\xf7\xd3\xc3\xca\xa7\xb0\xdc\n" (0415C7Ch)  
00413293 call       _printf (04110AFh)  
00413298 add         esp,4  
}
0041329B pop         edi  
0041329C pop         esi  
0041329D pop         ebx  
0041329E mov         esp,ebp  
004132A0 pop         ebp  
004132A1 ret  

可以很清楚的看到,对于if语句直接一个cmp比较,然后进行反着跳转,如果你要大于等于那么要跳转的jcc语句就是小于,跳转就是跳过if语句,如果跳转失败,就顺着执行下去。

纯if和else语句

纯if和else语句的意思,就是只有if 和else 没有else if这样的语句

void SingleIfElsetest(int a)
{
004116C0 push       ebp  
004116C1 mov         ebp,esp  
004116C3 sub         esp,40h  
004116C6 push       ebx  
004116C7 push       esi  
004116C8 push       edi  
if (a > 0)
004116C9 cmp         dword ptr [a],0  
004116CD jle         SingleIfElsetest+1Eh (04116DEh)  
{
printf("a>0\n");
004116CF push       offset string "a>0\n" (0415C8Ch)  
004116D4 call       _printf (04110AFh)  
004116D9 add         esp,4  
}
004116DC jmp         ___local_stdio_printf_options+0Bh (04116EBh)  
else
{
printf("a<=0\n");
004116DE push       offset string "a<=0\n" (0415C94h)  
004116E3 call       _printf (04110AFh)  
004116E8 add         esp,4  
}
}
004116EB pop         edi  
004116EC pop         esi  
004116ED pop         ebx  
004116EE mov         esp,ebp  
004116F0 pop         ebp  
004116F1 ret  

纯if else语句就是第一个条件满足进入后,然后跳转到else语句后的下一条指令

在if的语句中添加一个jmp

然后在if中添加一个相反的jcc指令来看是否跳转到else语句中

if和else还有else if混合

void BlendIfElsetest(int a)
{
004116C0 push       ebp  
004116C1 mov         ebp,esp  
004116C3 sub         esp,40h  
004116C6 push       ebx  
004116C7 push       esi  
004116C8 push       edi  
if (a > 0)
004116C9 cmp         dword ptr [a],0  
004116CD jle         BlendIfElsetest+1Eh (04116DEh)  
{
printf("a>0\n");
004116CF push       offset string "a>0\n" (0415C8Ch)  
004116D4 call       _printf (04110AFh)  
004116D9 add         esp,4  
}
004116DC jmp         ___local_stdio_printf_options (0411700h)  
else if (a == 0)
004116DE cmp         dword ptr [a],0  
004116E2 jne         ___local_stdio_printf_options+13h (04116F3h)  
{
printf("a==0\n");
004116E4 push       offset string "a==0\n" (0415C9Ch)  
004116E9 call       _printf (04110AFh)  
004116EE add         esp,4  
}
004116F1 jmp         ___local_stdio_printf_options (0411700h)  
else
{
printf("a<0\n");
004116F3 push       offset string "a<0\n" (0415CA4h)  
004116F8 call       _printf (04110AFh)  
004116FD add         esp,4  
}
}
00411700 pop         edi  
00411701 pop         esi  
00411702 pop         ebx  
00411703 mov         esp,ebp  
00411705 pop         ebp  
00411706 ret  

先进行第一个if语句的反向判断,如果jcc跳转成功就跳转到else if语句进行判断,如果else if反向判断成功就跳转到else执行else的语句,如果第一个if语句反向判断失败,就在执行完后jmp到else结束的下一条语句,如果第二个反向判断失败,就在执行完第二条指令后jmp到else结束的下一条指令

switch语句

简单的switch语句-只有三个case

void EasySwitchTest(int a)
{
00411710 push       ebp  
00411711 mov         ebp,esp  
00411713 sub         esp,44h  
00411716 push       ebx  
00411717 push       esi  
00411718 push       edi  
switch (a)
00411719 mov         eax,dword ptr [a]  
0041171C mov         dword ptr [ebp-44h],eax  
0041171F cmp         dword ptr [ebp-44h],1  
00411723 je         EasySwitchTest+23h (0411733h)  
00411725 cmp         dword ptr [ebp-44h],2  
00411729 je         EasySwitchTest+32h (0411742h)  
0041172B cmp         dword ptr [ebp-44h],3  
0041172F je         ___local_stdio_printf_options+1h (0411751h)  
00411731 jmp         ___local_stdio_printf_options+0Eh (041175Eh)  
{
case 1:
printf("结果为1\n");
00411733 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa1\n" (0415B34h)  
00411738 call       _printf (04110AFh)  
0041173D add         esp,4  
break;
00411740 jmp         ___local_stdio_printf_options+0Eh (041175Eh)  
case 2:
printf("结果为2\n");
00411742 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa2\n" (0415B40h)  
00411747 call       _printf (04110AFh)  
0041174C add         esp,4  
break;
0041174F jmp         ___local_stdio_printf_options+0Eh (041175Eh)  
case 3:
printf("结果为3\n");
00411751 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa3\n" (0415B4Ch)  
00411756 call       _printf (04110AFh)  
0041175B add         esp,4  
break;
default:
break;
}
}
0041175E pop         edi  
0041175F pop         esi  
00411760 pop         ebx  
00411761 mov         esp,ebp  
00411763 pop         ebp  
00411764 ret  

可以看到switch语句和if-else的区别,switch语句是直接拿来比较值,然后跳转到各个case的结果,而if-esle语句是一个一个比较跳转,switch语句更加简洁明了把各个case重新在比较跳转的下面来写

复杂的switch语句-大于3个case

void EasySwitchTest(int a)
{
00413BA0 push       ebp  
00413BA1 mov         ebp,esp  
00413BA3 sub         esp,44h  
00413BA6 push       ebx  
00413BA7 push       esi  
00413BA8 push       edi  
switch (a)
00413BA9 mov         eax,dword ptr [a]  
00413BAC mov         dword ptr [ebp-44h],eax  
00413BAF mov         ecx,dword ptr [ebp-44h]  
00413BB2 sub         ecx,1  
00413BB5 mov         dword ptr [ebp-44h],ecx  
00413BB8 cmp         dword ptr [ebp-44h],3  
00413BBC ja         $LN7+0Dh (0413C02h)  
00413BBE mov         edx,dword ptr [ebp-44h]  
00413BC1 jmp         dword ptr [edx*4+413C0Ch]  
{
case 1:
printf("结果为1\n");
00413BC8 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa1\n" (0415B34h)  
00413BCD call       _printf (04110AFh)  
00413BD2 add         esp,4  
break;
00413BD5 jmp         $LN7+0Dh (0413C02h)  
case 2:
printf("结果为2\n");
00413BD7 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa2\n" (0415B40h)  
00413BDC call       _printf (04110AFh)  
00413BE1 add         esp,4  
break;
00413BE4 jmp         $LN7+0Dh (0413C02h)  
case 3:
printf("结果为3\n");
00413BE6 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa3\n" (0415B4Ch)  
00413BEB call       _printf (04110AFh)  
00413BF0 add         esp,4  
break;
00413BF3 jmp         $LN7+0Dh (0413C02h)  
case 4:
printf("结果为4\n");
00413BF5 push       offset string "\xbd\xe1\xb9\xfb\xce\xaa4\n" (0415B58h)  
00413BFA call       _printf (04110AFh)  
00413BFF add         esp,4  
break;
default:
break;
}
}
00413C02 pop         edi  
00413C03 pop         esi  
00413C04 pop         ebx  
00413C05 mov         esp,ebp  
00413C07 pop         ebp  
00413C08 ret  
00413C09 nop         dword ptr [eax]  
00413C0C enter       413Bh,0  
00413C10 xlat       byte ptr [ebx]  
00413C11 cmp         eax,dword ptr [ecx]  
00413C14 out         3Bh,al  
00413C16 inc         ecx  
00413C17 add         ch,dh  
00413C19 cmp         eax,dword ptr [ecx]  

case语句的条件差值不大

对于case比较复杂的语句,编译器采用了两个表来处理。

一个是case地址表,另一个是case的地址索引表。

意思就是相当于地址索引表中存的是地址表的首地址

地址表

地址表中是从0开始作为索引,而且是用最大的case语句的条件值作为最大的数组范围,如果在其中有的是没有的,那么就用default或者switch的返回语句来填充

比如:

switch(a)
{
case 1:
case 3:
case 100:
case 101:
default:

}

这个就是其中的case2是没有的,但是地址表中依然会填充一个default或者switch结束的下一条语句的地址。然后地址表中会自动吧case的条件值进行排序,所以不需要对case值进行排序来使用

索引表

针对于case语句差距比较大,需要多个索引来处理,比如

switch(a)
{
case 1:

case 1000:
}

这里如果还是用地址表来存储,就需要1000个地址来处理了,所以这里引入了索引表

地址表还是有几个case存几个case,当然如果有default也要存,没有default就存一个switch结束语句的下一条语句的地址

然后索引表保存的是case的数组的数组寻址地址

比如这里就是

int a[1000];//假设a是索引表
a[0] = 0; //这里的索引表的数值表示的是地址表的索引的值
a[1]~a[999] = 不存在的case的下标
然后a[1000] = 1;//表示case 1000的索引表的下标

 

所以这里来讲上面那个比较复杂的case语句代码反汇编再来看就比较简单了,

    switch (a)
00413BA9 mov         eax,dword ptr [a]  
00413BAC mov         dword ptr [ebp-44h],eax  
00413BAF mov         ecx,dword ptr [ebp-44h]  
00413BB2 sub         ecx,1  
00413BB5 mov         dword ptr [ebp-44h],ecx  
00413BB8 cmp         dword ptr [ebp-44h],3  
00413BBC ja         $LN7+0Dh (0413C02h)  
00413BBE mov         edx,dword ptr [ebp-44h]  
00413BC1 jmp         dword ptr [edx*4+413C0Ch]  

首先将switch中的条件变量赋值给一个寄存器,这里是赋值给的ecx,然后ecx-1。这里的减一是为了跟地址表平齐,因为数组是从0开始数数的所以要减1。然后把条件变量的值跟最大的case条件语句进行比较好进行ja的JCC指令跳转,ja的意思是无符号数比较,左边大于右边就跳转。如果输入的是0或者是负数也是比这个大的所以也会跳转,而这里的跳转就直接跳转到default或者是switch语句的结束的下一条语句的地址。然后再进行把该条件变量的值减1后的值赋值给一个寄存器来保管,再通过相对基址寻址访问索引表再访问到地址表来跳转。

 

细心的你肯定发现,如果差的值很大会不会浪费?肯定是会浪费的于是有了另外一种处理办法。

case语句的条件差值很大

当最大的case值和最小的case值差距大于255的时候就会造成空间的浪费,所以这里采用一颗平衡二叉树来解决