滴水-day14-SWITCH反汇编-循环反汇编
Switch基础
switch的参数必须是整数表达式
case匹配的必须是常量表达式且不能重复
default是可以不写的,当case没有匹配上后会执行default
case后必须就break,不然程序会在匹配csae执行后继续往下执行case直到遇到break。
sub的立即数永远都是最小的那一个
SWITCH语句反汇编
连续且有序case
4个case且连续有序
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
00874435 mov eax,dword ptr [x]
00874438 mov dword ptr [ebp-0C4h],eax
0087443E mov ecx,dword ptr [ebp-0C4h]
00874444 sub ecx,1 // 使得参数减1
00874447 mov dword ptr [ebp-0C4h],ecx
0087444D cmp dword ptr [ebp-0C4h],3 // 比较参数减1后与3的大小
00874454 ja $LN7+0Fh (087449Fh) // 如果大于3直接跳转到default
00874456 mov edx,dword ptr [ebp-0C4h]
0087445C jmp dword ptr [edx*4+8744C0h] // 查表方式,通过参数与case 1的差值
// 这个称为大表,大表保存了所有case的情况,按顺序。
00874463 push offset string "1" (0877BCCh)
00874468 call _printf (08713ACh)
0087446D add esp,4
00874470 jmp $LN7+1Ch (08744ACh)
00874472 push offset string "2" (0877BD0h)
00874477 call _printf (08713ACh)
0087447C add esp,4
0087447F jmp $LN7+1Ch (08744ACh) // 每个case之后都有一个add esp,4.这是因为有push压栈操作
00874481 push offset string "3" (0877BD4h)
00874486 call _printf (08713ACh)
0087448B add esp,4
0087448E jmp $LN7+1Ch (08744ACh)
00874490 push offset string "4" (0877BD8h)
00874495 call _printf (08713ACh)
0087449A add esp,4
0087449D jmp $LN7+1Ch (08744ACh)
0087449F push offset string "Error" (0877BDCh)
008744A4 call _printf (08713ACh)
通过参数与第一个case的差值可以快速定位到需要执行的case分支,这与大表的排序方式有关。
在编译的时候大表会先确定有几个case语句,如果大表中存储了4个地址那么代表有4个case语句,
在减去case1之后的cmp比较的立即数可以理解为是case语句的数量,从0开始,如果ja条件满足就代表了参数与第一个case的差值大于case语句的数量,那么就说明了一定没有case与这个参数匹配,会直接走到default语句执行。
个人理解:通过参数与第一个case的差值可以快速定位到匹配的csae分支,cmp比较的是所有csae语句的数量,如果参数减去1后大于这个数量那么表明了这个参数不在case匹配范围内,会执行defalut。
大表的地址数量不绝对代表case语句的数量,当case判断无序的时候就不适用了。
3个case且有序连续
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
007C4435 mov eax,dword ptr [x]
007C4438 mov dword ptr [ebp-0C4h],eax
007C443E cmp dword ptr [ebp-0C4h],1
007C4445 je __$EncStackInitStart+3Fh (07C445Bh)
007C4447 cmp dword ptr [ebp-0C4h],2
007C444E je __$EncStackInitStart+4Eh (07C446Ah)
007C4450 cmp dword ptr [ebp-0C4h],3
007C4457 je __$EncStackInitStart+5Dh (07C4479h)
007C4459 jmp __$EncStackInitStart+6Ch (07C4488h)
007C445B push offset string "1" (07C7BCCh)
007C4460 call _printf (07C13ACh)
007C4465 add esp,4
007C4468 jmp __$EncStackInitStart+79h (07C4495h)
007C446A push offset string "2" (07C7BD0h)
007C446F call _printf (07C13ACh)
007C4474 add esp,4
007C4477 jmp __$EncStackInitStart+79h (07C4495h)
007C4479 push offset string "3" (07C7BD4h)
007C447E call _printf (07C13ACh)
007C4483 add esp,4
007C4486 jmp __$EncStackInitStart+79h (07C4495h)
007C4488 push offset string "Error" (07C7BE8h)
007C448D call _printf (07C13ACh)
通过观察发现当case只有3个分支的时候,汇编代码并没有创建大表进行查询而是通过if的方式去判断。
当je满足的时候会跳转到各自的地址处执行,如果都不满足执行jmp直接跳转到defalut执行。
PS:当判断条件小于4个的时候没必要使用case,因为在汇编代码看来这与if没什么区别。
4个csae不连续
case 2:
printf("1");
break;
case 5:
printf("2");
break;
case 7:
printf("3");
break;
case 9:
printf("4");
break;
00CB1845 mov eax,dword ptr [x]
00CB1848 mov dword ptr [ebp-0C4h],eax
00CB184E mov ecx,dword ptr [ebp-0C4h]
00CB1854 sub ecx,2
00CB1857 mov dword ptr [ebp-0C4h],ecx
00CB185D cmp dword ptr [ebp-0C4h],7 // 7个,为何是7个?case明明是4个
00CB1864 ja $LN7+0Fh (0CB18AFh)
00CB1866 mov edx,dword ptr [ebp-0C4h]
00CB186C jmp dword ptr [edx*4+0CB18D0h]
00CB1873 push offset string "1" (0CB7B30h)
00CB1878 call _printf (0CB10D2h)
00CB187D add esp,4
00CB1880 jmp $LN7+1Ch (0CB18BCh)
00CB1882 push offset string "2" (0CB7B34h)
00CB1887 call _printf (0CB10D2h)
00CB188C add esp,4
00CB188F jmp $LN7+1Ch (0CB18BCh)
00CB1891 push offset string "3" (0CB7B38h)
00CB1896 call _printf (0CB10D2h)
00CB189B add esp,4
00CB189E jmp $LN7+1Ch (0CB18BCh)
00CB18A0 push offset string "4" (0CB7B3Ch)
00CB18A5 call _printf (0CB10D2h)
00CB18AA add esp,4
00CB18AD jmp $LN7+1Ch (0CB18BCh)
00CB18AF push offset string "Error" (0CB7B40h)
00CB18B4 call _printf (0CB10D2h)
0x00CB18D0 73 18 cb 00 // 2
0x00CB18D4 af 18 cb 00 // default
0x00CB18D8 af 18 cb 00 // default
0x00CB18DC 82 18 cb 00 // 5
0x00CB18E0 af 18 cb 00 // default
0x00CB18E4 91 18 cb 00 // 7
0x00CB18E8 af 18 cb 00 // default
0x00CB18EC a0 18 cb 00 // 9
通过观察汇编与大表可以发现,case判断无序的时候中间缺少的条件依然会添加到大表中用来占位置,占位置的这个地址是defalut的地址。也就是说不管case是不是有序无序,大表的排序依然不变还是按照从上往下的顺序排序,中间缺少的值就用defalut的地址代替。
case判断无序差值在一定范围内的话还是会使用大表的形式进行匹配值。
经过测试:
当两个case差值小于7的时候大表会创建并保存所有case的情况,包括缺少的会被填充为default。
当大于等于7的时候会有大表和小表。
当有小表的时候通常会通过小表查询的方式去确定匹配的case
倒序case
case 100:
printf("1");
break;
case 99:
printf("5");
break;
case 98:
printf("7");
break;
case 97:
printf("9");
break;
case 96:
printf("96");
break;
循环
do...while.....反汇编分析
1
int i = 0;
do
{
++i;
} while (i < 5);
004A53A0 push ebp
004A53A1 mov ebp,esp
004A53A3 sub esp,0CCh
004A53A9 push ebx
004A53AA push esi
004A53AB push edi
004A53AC lea edi,[ebp-0Ch]
004A53AF mov ecx,3
004A53B4 mov eax,0CCCCCCCCh
004A53B9 rep stos dword ptr es:[edi]
004A53BB mov ecx,4AC029h
004A53C0 call 004A1320
004A53C5 mov dword ptr [ebp-8],0 // 局部变量赋值
004A53CC mov eax,dword ptr [ebp-8]
004A53CF add eax,1 // do体执行
004A53D2 mov dword ptr [ebp-8],eax
004A53D5 cmp dword ptr [ebp-8],5
004A53D9 jl 004A53CC // 判断条件
004A53DB pop edi
004A53DC pop esi
004A53DD pop ebx
004A53DE add esp,0CCh
004A53E4 cmp ebp,esp
004A53E6 call 004A1249
004A53EB mov esp,ebp
004A53ED pop ebp
004A53EE ret
2
void doWhile(int x)
{
do
{
printf("%d", x);
++x;
} while (x < 5);
}
003319D1 mov eax,dword ptr [x] // 移动参数到eax
003319D4 push eax
003319D5 push offset string "%d" (0337B44h) // 打印参数
003319DA call _printf (03310D2h)
003319DF add esp,8
003319E2 mov eax,dword ptr [x] //++x;执行
003319E5 add eax,1
003319E8 mov dword ptr [x],eax
003319EB cmp dword ptr [x],5
003319EF jl __$EncStackInitStart+15h (03319D1h) // 条件判断,小于5就跳转
003319F1 pop edi
003319F2 pop esi
003319F3 pop ebx
通过条件判断可以确定循环的起始位置
While
void whiles(int x )
{
while (x < 5)
{
printf("%d", x);
++x;
}
}
00041921 cmp dword ptr [x],5 // x参数
00041925 jge __JustMyCode_Default+23h (041943h) // 大于等于。条件判断
00041927 mov eax,dword ptr [x]
0004192A push eax
0004192B push offset string "%d" (047B44h) //打印参数
00041930 call _printf (0410D2h)
00041935 add esp,8
00041938 mov eax,dword ptr [x]
0004193B add eax,1
0004193E mov dword ptr [x],eax
00041941 jmp __JustMyCode_Default+1h (041921h)
00041943 pop edi
00041944 pop esi
00041945 pop ebx
通过条件判断可以确定循环结束地址,通过jmp可以确定循环起始地址