文章

滴水-day14-SWITCH反汇编-循环反汇编

Switch基础

  1. switch的参数必须是整数表达式

  2. case匹配的必须是常量表达式且不能重复

  3. default是可以不写的,当case没有匹配上后会执行default

  4. 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可以确定循环起始地址

For循环

day14.xlsx

License:  CC BY 4.0