C语言逆向分析——Switch语句,为何大多数情况较if语句更高效?就是因为查找表

Switch语句

Switch语句也是分支语句的一种,其语法如下:

switch(表达式)
{
case 常量表达式1:
语句;
break;
case 常量表达式:
语句;
break;
case 常量表达式:
语句;
break;
......
default:
语句;
break;
}

需要注意如下几点:

  1. 表达式结束不能是浮点数

  2. case后的常量值不能一样

  3. case后的值必须是常量

  4. break非常重要,当执行到一个分支后,如果没有break就会继续向下执行,遇到break才会跳出switch语句

  5. default语句与位置无关,但是当default写在其他条件的前面时。如果没有break就会向下继续匹配执行

switch语句与if..else语句的区别:

  1. switch语句只能进行等值判断(仅能支持常量表达式),而if..else可以进行区间判断(表达式、常量...都可以)

  2. switch语句的执行效率远远高于if..else,在分支条件比较多的情况下,这种趋势愈发明显

Switch语句为什么高效

之前说到switch语句的执行效率远远高于if..else,这是为什么?我们可以写一段代码通过反汇编来查看其在底层的具体实现:

int x = 3;
 
switch(x)
{
case 1:
printf("A \n");
break;
case 2:
printf("B \n");
break;
case 3:
printf("C \n");
break;
case 4:
printf("D \n");
break;
default:
printf("default \n");
break;
}

其反汇编代码为:

images/download/attachments/12714021/image2021-2-22_0-40-17.png

如上反汇编代码我们可以看出switch在一开始就直接将变量x-1与3进行比较,ja指令则表示大于则跳转(a表示above),但这变量x明显等于3,所以将值赋予edx,最后jmp跳到堆栈地址2*4+40d896(40d896+8),也就是0x40D89E对应的值

images/download/attachments/12714021/image2021-2-22_0-51-14.png

最终跳转到0x0040d80c,打印出了C。

 

我自己机器上的实验:

 

 

为啥本质上是查找表呢?我自己机器上分析下:

 

 

 

那么我们再使用if...else来实现相同功能:

int x = 3;
 
if (x == 1) {
printf("A \n");
} else if (x == 2) {
printf("B \n");
} else if (x == 3) {
printf("C \n");
} else if (x == 4) {
printf("D \n");
} else {
printf("default \n");
}

其反汇编代码为:

images/download/attachments/12714021/image2021-2-22_0-43-25.png

可以看见其会一行一行的执行,而不像switch语句一样,直接可以跳转到对应的地址。

通过观察堆栈,我们可以发现switch的高明之处:

images/download/attachments/12714021/image2021-2-22_0-53-37.png

其将case分支的执行地址都存入到了堆栈中(也就是查找表),而后使用算法去寻找到对应要执行的地址(堆栈存储的地址),这套算法我们可以这样理解:

1.堆栈中会存在一个执行地址的内存表,其顺序是按照case后的常量大小排序的,最大的常量所在的分支执行地址最先压入表中;如下图所示我将代码顺序打乱后还是如此:

images/download/attachments/12714021/image2021-2-22_1-14-30.png

2.根据内存表存储顺序,直接将传入的值减去1,然后*4(这里也是数据宽度4字节)+最后压入的堆栈地址。

了解完算法原理之后,我们需要了解一个概念:大表和小表;当生成的函数跳转地址表,每个成员有4个字节的时,我们称之为大表。

注意

  1. 在case分支小于等于3个、case最大值和最小值差大于等于255时不存在大小表

  2. 在case分支大于3个并且大表项空隙小于等于6个时只有大表没有小表

  3. 在case分支大于3个且大表项空隙大于6个且case最大最小差值小于255时存在大表和小表

最后:在一般情况下,我们通常会去使用if语句而不是switch,因为if语句在编程时更加得心应手,所以我们可以暂且忽视效率而去选择更加便利的编写方式。

 

实例

case分支少的switch语句

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 1:
			printf("%d",1);
			break;
		case 2:
			printf("%d",2);
			break;
		case 3:
			printf("%d",3);
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(2);
	return 0;
}

反汇编:

Fun:
0040D490   push        ebp
0040D491   mov         ebp,esp
0040D493   sub         esp,44h
0040D496   push        ebx
0040D497   push        esi
0040D498   push        edi
0040D499   lea         edi,[ebp-44h]
0040D49C   mov         ecx,11h
0040D4A1   mov         eax,0CCCCCCCCh
0040D4A6   rep stos    dword ptr [edi]
0040D4A8   mov         eax,dword ptr [ebp+8]
0040D4AB   mov         dword ptr [ebp-4],eax
0040D4AE   cmp         dword ptr [ebp-4],1
0040D4B2   je          Fun+32h (0040d4c2)
0040D4B4   cmp         dword ptr [ebp-4],2
0040D4B8   je          Fun+43h (0040d4d3)
0040D4BA   cmp         dword ptr [ebp-4],3
0040D4BE   je          Fun+54h (0040d4e4)
0040D4C0   jmp         Fun+65h (0040d4f5)
0040D4C2   push        1
0040D4C4   push        offset string "%d" (0042210c)
0040D4C9   call        printf (0040d760)
0040D4CE   add         esp,8
0040D4D1   jmp         Fun+72h (0040d502)
0040D4D3   push        2
0040D4D5   push        offset string "%d" (0042210c)
0040D4DA   call        printf (0040d760)
0040D4DF   add         esp,8
0040D4E2   jmp         Fun+72h (0040d502)
0040D4E4   push        3
0040D4E6   push        offset string "%d" (0042210c)
0040D4EB   call        printf (0040d760)
0040D4F0   add         esp,8
0040D4F3   jmp         Fun+72h (0040d502)
0040D4F5   push        offset string "None" (00422f6c)
0040D4FA   call        printf (0040d760)
0040D4FF   add         esp,4
0040D502   pop         edi
0040D503   pop         esi
0040D504   pop         ebx
0040D505   add         esp,44h
0040D508   cmp         ebp,esp
0040D50A   call        __chkesp (004010a0)
0040D50F   mov         esp,ebp
0040D511   pop         ebp
0040D512   ret

小结:

case分支小于4个时,其反汇编与if...else类似

case分支多的switch语句

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 1:
			printf("1");
			break;
		case 2:
			printf("2");
			break;
		case 3:
			printf("3");
			break;
		case 4:
			printf("4");
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(2);
	return 0;
}

反汇编:

Fun:
0040D490   push        ebp
0040D491   mov         ebp,esp
0040D493   sub         esp,44h
0040D496   push        ebx
0040D497   push        esi
0040D498   push        edi
0040D499   lea         edi,[ebp-44h]
0040D49C   mov         ecx,11h
0040D4A1   mov         eax,0CCCCCCCCh
0040D4A6   rep stos    dword ptr [edi]
0040D4A8   mov         eax,dword ptr [ebp+8]
0040D4AB   mov         dword ptr [ebp-4],eax
0040D4AE   mov         ecx,dword ptr [ebp-4]
0040D4B1   sub         ecx,1
0040D4B4   mov         dword ptr [ebp-4],ecx	;[ebp-4]=[ebp+8]-1
0040D4B7   cmp         dword ptr [ebp-4],3
0040D4BB   ja          $L537+11h (0040d50b)		;如果[ebp-4]>3跳转到0040d50b
0040D4BD   mov         edx,dword ptr [ebp-4]
0040D4C0   jmp         dword ptr [edx*4+40D529h]
$L533:
0040D4C7   push        offset string "1" (00422fac)
0040D4CC   call        printf (0040d760)
0040D4D1   add         esp,4
0040D4D4   jmp         $L539+1Ch (0040d510)
$L535:
0040D4D6   push        offset string "2" (00422fa8)
0040D4DB   call        printf (0040d760)
0040D4E0   add         esp,4
0040D4E3   jmp         $L539+1Ch (0040d510)
$L537:
0040D4E5   push        offset string "3" (00422fa4)
0040D4EA   call        printf (0040d760)
0040D4EF   add         esp,4
0040D4F2   jmp         $L539+1Ch (0040d510)
$L539:
0040D4F4   push        offset string "4" (0042210c)
0040D4F9   call        printf (0040d760)
0040D4FE   add         esp,4
0040D501   jmp         $L539+1Ch (0040d510)
0040D503   push        offset string "None" (00422f6c)
0040D508   call        printf (0040d760)
0040D50D   add         esp,4
0040D510   pop         edi
0040D511   pop         esi
0040D512   pop         ebx
0040D513   add         esp,44h
0040D516   cmp         ebp,esp
0040D518   call        __chkesp (004010a0)
0040D51D   mov         esp,ebp
0040D51F   pop         ebp
0040D520   ret

Register:

 EAX = 00000002 EBX = 7FFDE000
 ECX = 00000001 EDX = 00000001
 ESI = 00000000 EDI = 0012FF28
 EIP = 0040D4C0 ESP = 0012FED8
 EBP = 0012FF28 EFL = 00000293

Memory:

0040D521  C7 D4 40 00  窃@.
0040D525  D6 D4 40 00  衷@.
0040D529  E5 D4 40 00  逶@.
0040D52D  F4 D4 40 00  粼@.

小结:

case分支大于等于4个且连续时,会在一段连续内存中存储每个case所对应语句块的起始地址(称其为大表),根据[edx*4+40D529h]即可确定跳转地址

case后的值打乱顺序

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 2:
			printf("2");
			break;
		case 1:
			printf("1");
			break;
		case 4:
			printf("4");
			break;
		case 3:
			printf("3");
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(2);
	return 0;
}

反汇编:

Fun:
0040D490   push        ebp
0040D491   mov         ebp,esp
0040D493   sub         esp,44h
0040D496   push        ebx
0040D497   push        esi
0040D498   push        edi
0040D499   lea         edi,[ebp-44h]
0040D49C   mov         ecx,11h
0040D4A1   mov         eax,0CCCCCCCCh
0040D4A6   rep stos    dword ptr [edi]
0040D4A8   mov         eax,dword ptr [ebp+8]
0040D4AB   mov         dword ptr [ebp-4],eax
0040D4AE   mov         ecx,dword ptr [ebp-4]
0040D4B1   sub         ecx,1
0040D4B4   mov         dword ptr [ebp-4],ecx
0040D4B7   cmp         dword ptr [ebp-4],3
0040D4BB   ja          $L539+0Fh (0040d503)
0040D4BD   mov         edx,dword ptr [ebp-4]
0040D4C0   jmp         dword ptr [edx*4+40D521h]
$L533:
0040D4C7   push        offset string "2" (00422fac)
0040D4CC   call        printf (0040d760)
0040D4D1   add         esp,4
0040D4D4   jmp         $L539+1Ch (0040d510)
$L535:
0040D4D6   push        offset string "1" (00422fa8)
0040D4DB   call        printf (0040d760)
0040D4E0   add         esp,4
0040D4E3   jmp         $L539+1Ch (0040d510)
$L537:
0040D4E5   push        offset string "4" (00422fa4)
0040D4EA   call        printf (0040d760)
0040D4EF   add         esp,4
0040D4F2   jmp         $L539+1Ch (0040d510)
$L539:
0040D4F4   push        offset string "3" (0042210c)
0040D4F9   call        printf (0040d760)
0040D4FE   add         esp,4
0040D501   jmp         $L539+1Ch (0040d510)
0040D503   push        offset string "None" (00422f6c)
0040D508   call        printf (0040d760)
0040D50D   add         esp,4
0040D510   pop         edi
0040D511   pop         esi
0040D512   pop         ebx
0040D513   add         esp,44h
0040D516   cmp         ebp,esp
0040D518   call        __chkesp (004010a0)
0040D51D   mov         esp,ebp
0040D51F   pop         ebp
0040D520   ret

Register:

 EAX = 00000002 EBX = 7FFDB000
 ECX = 00000001 EDX = 00000001
 ESI = 00000000 EDI = 0012FF28
 EIP = 0040D4C0 ESP = 0012FED8
 EBP = 0012FF28 EFL = 00000293

Memory:

0040D521  D6 D4 40 00  衷@.
0040D525  C7 D4 40 00  窃@.
0040D529  F4 D4 40 00  粼@.
0040D52D  E5 D4 40 00  逶@.

小结:

在值连续的情况下,顺序并不会影响生成大表

起始值对反汇编的影响

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 100:
			printf("100");
			break;
		case 101:
			printf("101");
			break;
		case 102:
			printf("102");
			break;
		case 103:
			printf("103");
			break;
		case 104:
			printf("104");
			break;
		case 105:
			printf("105");
			break;
		case 106:
			printf("106");
			break;
		case 107:
			printf("107");
			break;
		case 108:
			printf("108");
			break;
		case 109:
			printf("109");
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(102);
	return 0;
}

反汇编:

Fun:
0040D7E0   push        ebp
0040D7E1   mov         ebp,esp
0040D7E3   sub         esp,44h
0040D7E6   push        ebx
0040D7E7   push        esi
0040D7E8   push        edi
0040D7E9   lea         edi,[ebp-44h]
0040D7EC   mov         ecx,11h
0040D7F1   mov         eax,0CCCCCCCCh
0040D7F6   rep stos    dword ptr [edi]
0040D7F8   mov         eax,dword ptr [ebp+8]
0040D7FB   mov         dword ptr [ebp-4],eax
0040D7FE   mov         ecx,dword ptr [ebp-4]
0040D801   sub         ecx,64h	;64h=100
0040D804   mov         dword ptr [ebp-4],ecx
0040D807   cmp         dword ptr [ebp-4],9
0040D80B   ja          $L551+0Fh (0040d8b7)
0040D811   mov         edx,dword ptr [ebp-4]
0040D814   jmp         dword ptr [edx*4+40D8D5h]
$L533:
0040D81B   push        offset string "100" (00422fc4)
0040D820   call        printf (0040d760)
0040D825   add         esp,4
0040D828   jmp         $L551+1Ch (0040d8c4)
$L535:
0040D82D   push        offset string "101" (00422fc0)
0040D832   call        printf (0040d760)
0040D837   add         esp,4
0040D83A   jmp         $L551+1Ch (0040d8c4)
$L537:
0040D83F   push        offset string "102" (00422fbc)
0040D844   call        printf (0040d760)
0040D849   add         esp,4
0040D84C   jmp         $L551+1Ch (0040d8c4)
$L539:
0040D84E   push        offset string "103" (00422fb8)
0040D853   call        printf (0040d760)
0040D858   add         esp,4
0040D85B   jmp         $L551+1Ch (0040d8c4)
$L541:
0040D85D   push        offset string "104" (00422fb4)
0040D862   call        printf (0040d760)
0040D867   add         esp,4
0040D86A   jmp         $L551+1Ch (0040d8c4)
$L543:
0040D86C   push        offset string "105" (00422fb0)
0040D871   call        printf (0040d760)
0040D876   add         esp,4
0040D879   jmp         $L551+1Ch (0040d8c4)
$L545:
0040D87B   push        offset string "106" (00422fac)
0040D880   call        printf (0040d760)
0040D885   add         esp,4
0040D888   jmp         $L551+1Ch (0040d8c4)
$L547:
0040D88A   push        offset string "107" (00422fa8)
0040D88F   call        printf (0040d760)
0040D894   add         esp,4
0040D897   jmp         $L551+1Ch (0040d8c4)
$L549:
0040D899   push        offset string "108" (00422fa4)
0040D89E   call        printf (0040d760)
0040D8A3   add         esp,4
0040D8A6   jmp         $L551+1Ch (0040d8c4)
$L551:
0040D8A8   push        offset string "109" (0042210c)
0040D8AD   call        printf (0040d760)
0040D8B2   add         esp,4
0040D8B5   jmp         $L551+1Ch (0040d8c4)
0040D8B7   push        offset string "None" (00422f6c)
0040D8BC   call        printf (0040d760)
0040D8C1   add         esp,4
0040D8C4   pop         edi
0040D8C5   pop         esi
0040D8C6   pop         ebx
0040D8C7   add         esp,44h
0040D8CA   cmp         ebp,esp
0040D8CC   call        __chkesp (004010a0)
0040D8D1   mov         esp,ebp
0040D8D3   pop         ebp
0040D8D4   ret

Memory:

0040D8D5  1B D8 40 00  .谸.
0040D8D9  2D D8 40 00  -谸.
0040D8DD  3F D8 40 00  ?谸.
0040D8E1  4E D8 40 00  N谸.
0040D8E5  5D D8 40 00  ]谸.
0040D8E9  6C D8 40 00  l谸.
0040D8ED  7B D8 40 00  {谸.
0040D8F1  8A D8 40 00  娯@.
0040D8F5  99 D8 40 00  欂@.
0040D8F9  A8 D8 40 00  ㄘ@.

Register:

 EAX = 00000066 EBX = 7FFDB000
 ECX = 00000002 EDX = 00000002
 ESI = 00000000 EDI = 0012FF28
 EIP = 0040D814 ESP = 0012FED8
 EBP = 0012FF28 EFL = 00000297

小结:

起始值并不影响大表的生成,edx = switch表达式的值 - case整型常量起始值

连续值中抹去少项

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 100:
			printf("100");
			break;
		case 101:
			printf("101");
			break;
		case 102:
			printf("102");
			break;
		case 103:
			printf("103");
			break;
		case 106:
			printf("106");
			break;
		case 107:
			printf("107");
			break;
		case 108:
			printf("108");
			break;
		case 109:
			printf("109");
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(102);
	return 0;
}

反汇编:

Fun:
0040D7E0   push        ebp
0040D7E1   mov         ebp,esp
0040D7E3   sub         esp,44h
0040D7E6   push        ebx
0040D7E7   push        esi
0040D7E8   push        edi
0040D7E9   lea         edi,[ebp-44h]
0040D7EC   mov         ecx,11h
0040D7F1   mov         eax,0CCCCCCCCh
0040D7F6   rep stos    dword ptr [edi]
0040D7F8   mov         eax,dword ptr [ebp+8]
0040D7FB   mov         dword ptr [ebp-4],eax
0040D7FE   mov         ecx,dword ptr [ebp-4]
0040D801   sub         ecx,64h
0040D804   mov         dword ptr [ebp-4],ecx
0040D807   cmp         dword ptr [ebp-4],9
0040D80B   ja          $L547+0Fh (0040d893)
0040D811   mov         edx,dword ptr [ebp-4]
0040D814   jmp         dword ptr [edx*4+40D8B1h]
$L533:
0040D81B   push        offset string "100" (00422fbc)
0040D820   call        printf (0040d760)
0040D825   add         esp,4
0040D828   jmp         $L547+1Ch (0040d8a0)
$L535:
0040D82A   push        offset string "101" (00422fb8)
0040D82F   call        printf (0040d760)
0040D834   add         esp,4
0040D837   jmp         $L547+1Ch (0040d8a0)
$L537:
0040D839   push        offset string "102" (00422fb4)
0040D83E   call        printf (0040d760)
0040D843   add         esp,4
0040D846   jmp         $L547+1Ch (0040d8a0)
$L539:
0040D848   push        offset string "103" (00422fb0)
0040D84D   call        printf (0040d760)
0040D852   add         esp,4
0040D855   jmp         $L547+1Ch (0040d8a0)
$L541:
0040D857   push        offset string "106" (00422fac)
0040D85C   call        printf (0040d760)
0040D861   add         esp,4
0040D864   jmp         $L547+1Ch (0040d8a0)
$L543:
0040D866   push        offset string "107" (00422fa8)
0040D86B   call        printf (0040d760)
0040D870   add         esp,4
0040D873   jmp         $L547+1Ch (0040d8a0)
$L545:
0040D875   push        offset string "108" (00422fa4)
0040D87A   call        printf (0040d760)
0040D87F   add         esp,4
0040D882   jmp         $L547+1Ch (0040d8a0)
$L547:
0040D884   push        offset string "109" (0042210c)
0040D889   call        printf (0040d760)
0040D88E   add         esp,4
0040D891   jmp         $L547+1Ch (0040d8a0)
0040D893   push        offset string "None" (00422f6c)
0040D898   call        printf (0040d760)
0040D89D   add         esp,4
0040D8A0   pop         edi
0040D8A1   pop         esi
0040D8A2   pop         ebx
0040D8A3   add         esp,44h
0040D8A6   cmp         ebp,esp
0040D8A8   call        __chkesp (004010a0)
0040D8AD   mov         esp,ebp
0040D8AF   pop         ebp
0040D8B0   ret

Memory:

0040D8B1  1B D8 40 00  .谸.
0040D8B5  2A D8 40 00  *谸.
0040D8B9  39 D8 40 00  9谸.
0040D8BD  48 D8 40 00  H谸.
0040D8C1  93 D8 40 00  撠@.	;缺失的104和105都跳转到0040D893
0040D8C5  93 D8 40 00  撠@.	;而0040D893是Default的语句块地址
0040D8C9  57 D8 40 00  W谸.
0040D8CD  66 D8 40 00  f谸.
0040D8D1  75 D8 40 00  u谸.
0040D8D5  84 D8 40 00  勜@.

Register:

 EAX = 00000066 EBX = 7FFDE000
 ECX = 00000002 EDX = 00000002
 ESI = 00000000 EDI = 0012FF28
 EIP = 0040D814 ESP = 0012FED8
 EBP = 0012FF28 EFL = 00000297



小结:

空缺地址通过填充default语句块地址解决,但会造成内存浪费

连续值中抹去多项

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 100:
			printf("100");
			break;
		case 101:
			printf("101");
			break;
		case 102:
			printf("102");
			break;
		case 106:
			printf("106");
			break;
		case 108:
			printf("108");
			break;
		case 112:
			printf("112");
			break;
		case 115:
			printf("115");
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(110);
	return 0;
}

反汇编:

Fun:
0040D7E0   push        ebp
0040D7E1   mov         ebp,esp
0040D7E3   sub         esp,44h
0040D7E6   push        ebx
0040D7E7   push        esi
0040D7E8   push        edi
0040D7E9   lea         edi,[ebp-44h]
0040D7EC   mov         ecx,11h
0040D7F1   mov         eax,0CCCCCCCCh
0040D7F6   rep stos    dword ptr [edi]
0040D7F8   mov         eax,dword ptr [ebp+8]
0040D7FB   mov         dword ptr [ebp-4],eax
0040D7FE   mov         ecx,dword ptr [ebp-4]
0040D801   sub         ecx,64h
0040D804   mov         dword ptr [ebp-4],ecx
0040D807   cmp         dword ptr [ebp-4],0Fh
0040D80B   ja          $L545+0Fh (0040d888)
0040D80D   mov         eax,dword ptr [ebp-4]
0040D810   xor         edx,edx				;edx清零
0040D812   mov         dl,byte ptr  (0040d8c6)[eax]	;dl=[小表地址+eax] ==>没有看懂。。。todo
0040D818   jmp         dword ptr [edx*4+40D8A6h]
$L533:
0040D81F   push        offset string "102" (00422fb4)
0040D824   call        printf (0040d760)
0040D829   add         esp,4
0040D82C   jmp         $L545+1Ch (0040d895)
$L535:
0040D82E   push        offset string "1003\n" (00422fd8)
0040D833   call        printf (0040d760)
0040D838   add         esp,4
0040D83B   jmp         $L545+1Ch (0040d895)
$L537:
0040D83D   push        offset string "109" (00422fac)
0040D842   call        printf (0040d760)
0040D847   add         esp,4
0040D84A   jmp         $L545+1Ch (0040d895)
$L539:
0040D84C   push        offset string "1009\n" (00422fd0)
0040D851   call        printf (0040d760)
0040D856   add         esp,4
0040D859   jmp         $L545+1Ch (0040d895)
$L541:
0040D85B   push        offset string "108" (00422fa4)
0040D860   call        printf (0040d760)
0040D865   add         esp,4
0040D868   jmp         $L545+1Ch (0040d895)
$L543:
0040D86A   push        offset string "109" (00422fc8)
0040D86F   call        printf (0040d760)
0040D874   add         esp,4
0040D877   jmp         $L545+1Ch (0040d895)
$L545:
0040D879   push        offset string "115" (00422fc0)
0040D87E   call        printf (0040d760)
0040D883   add         esp,4
0040D886   jmp         $L545+1Ch (0040d895)
0040D888   push        offset string "error\n" (00422f6c)
0040D88D   call        printf (0040d760)
0040D892   add         esp,4
0040D895   pop         edi
0040D896   pop         esi
0040D897   pop         ebx
0040D898   add         esp,44h
0040D89B   cmp         ebp,esp
0040D89D   call        __chkesp (004010a0)
0040D8A2   mov         esp,ebp
0040D8A4   pop         ebp
0040D8A5   ret

Memory:

;大表
0040D8A6  1F D8 40 00  .谸.
0040D8AA  2E D8 40 00  .谸.
0040D8AE  3D D8 40 00  =谸.
0040D8B2  4C D8 40 00  L谸.
0040D8B6  5B D8 40 00  [谸.
0040D8BA  6A D8 40 00  j谸.
0040D8BE  79 D8 40 00  y谸.
0040D8C2  88 D8 40 00  堌@.
;小表
0040D8C6  00 01 02 07  ....
0040D8CA  07 07 03 07  ....
0040D8CE  04 07 07 07  ....
0040D8D2  05 07 07 06  ....

小表的解释:

  • 当空缺值太多时内存的浪费也会变多,编译器当然知道这样不是办法,所以利用小表来解决这个问题。小表可以看作是一个智能蹦床,对于不同的玩家会给出不同的力,遇到没有付费的玩家(空缺值)直接将他抛出场外(给出参数,使其跳转到default的语句块),遇到付费玩家(存在的值)则按照他的等级给出不同的力(给出参数,使其跳转到其对应的语句块)

  • 可以看出,在小表中所有的空缺值都是07(因为在这个样例中,当edx=7[edx*4+40D8A6h]的地址为default语句块的地址),而存在的值的对应值从0递增。

 

断开一定程度的选择

在这里插入图片描述

在这里插入图片描述
关键点

mov dl,byte ptr (004010d0)[eax]

jmp dword ptr [edx4+4010BCh]
(004010d0)[eax] = [004010d0 + eax
4] 在这个地址中得到一个偏移 作为查询大表的edx值。==》内在原理是啥???todo

 

 

差值大的情况

CPP代码:

#include "stdafx.h"
void Fun(int x) {
	switch (x) {
		case 301:
			printf("301");
			break;
		case 302:
			printf("302");
			break;
		case 303:
			printf("303");
			break;
		case 304:
			printf("304");
			break;
		case 305:
			printf("305");
			break;
		case 306:
			printf("306");
			break;
		case 307:
			printf("307");
			break;
		case 308:
			printf("308");
			break;
		case 309:
			printf("309");
			break;
		case 3:
			printf("3");
			break;
		default:
			printf("None");
			break;
	}
}

int main(int argc, char* argv[]) {
	Fun(102);
	return 0;
}

反汇编:

Fun:
0040D7E0   push        ebp
0040D7E1   mov         ebp,esp
0040D7E3   sub         esp,44h
0040D7E6   push        ebx
0040D7E7   push        esi
0040D7E8   push        edi
0040D7E9   lea         edi,[ebp-44h]
0040D7EC   mov         ecx,11h
0040D7F1   mov         eax,0CCCCCCCCh
0040D7F6   rep stos    dword ptr [edi]
0040D7F8   mov         eax,dword ptr [ebp+8]
0040D7FB   mov         dword ptr [ebp-4],eax
0040D7FE   cmp         dword ptr [ebp-4],131h	;0x131 = 305
0040D805   jg          Fun+75h (0040d855)		;[ebp-4] > 305 跳转到0040d855
0040D807   cmp         dword ptr [ebp-4],131h
0040D80E   je          Fun+0D7h (0040d8b7)		;[ebp-4] == 305 跳转到0040d8b7
0040D814   cmp         dword ptr [ebp-4],12Eh	;0x12E = 302
0040D81B   jg          Fun+5Eh (0040d83e)		;[ebp-4] > 302 跳转到0040d855
0040D81D   cmp         dword ptr [ebp-4],12Eh
0040D824   je          Fun+0A7h (0040d887)		;[ebp-4] == 302 跳转到0040d887
0040D826   cmp         dword ptr [ebp-4],3;   3比较
0040D82A   je          $L549+0Fh (0040d902)
0040D830   cmp         dword ptr [ebp-4],12Dh; ==>301比较
0040D837   je          Fun+95h (0040d875)
0040D839   jmp         $L549+1Eh (0040d911)
0040D83E   cmp         dword ptr [ebp-4],12Fh
0040D845   je          Fun+0B9h (0040d899)
0040D847   cmp         dword ptr [ebp-4],130h;==>304比较
0040D84E   je          Fun+0C8h (0040d8a8)
0040D850   jmp         $L549+1Eh (0040d911)
0040D855   mov         ecx,dword ptr [ebp-4]
0040D858   sub         ecx,132h ;306比较
0040D85E   mov         dword ptr [ebp-4],ecx
0040D861   cmp         dword ptr [ebp-4],3
0040D865   ja          $L549+1Eh (0040d911)
0040D86B   mov         edx,dword ptr [ebp-4]
0040D86E   jmp         dword ptr [edx*4+40D92Fh] ;==>这里也有表
0040D875   push        offset string "301" (00422fc4)
0040D87A   call        printf (0040d760)
0040D87F   add         esp,4
0040D882   jmp         $L549+2Bh (0040d91e)
0040D887   push        offset string "302" (00422fc0)
0040D88C   call        printf (0040d760)
0040D891   add         esp,4
0040D894   jmp         $L549+2Bh (0040d91e)
0040D899   push        offset string "303" (00422fbc)
0040D89E   call        printf (0040d760)
0040D8A3   add         esp,4
0040D8A6   jmp         $L549+2Bh (0040d91e)
0040D8A8   push        offset string "304" (00422fb8)
0040D8AD   call        printf (0040d760)
0040D8B2   add         esp,4
0040D8B5   jmp         $L549+2Bh (0040d91e)
0040D8B7   push        offset string "305" (00422fb4)
0040D8BC   call        printf (0040d760)
0040D8C1   add         esp,4
0040D8C4   jmp         $L549+2Bh (0040d91e)
$L543:
0040D8C6   push        offset string "306" (00422fb0)
0040D8CB   call        printf (0040d760)
0040D8D0   add         esp,4
0040D8D3   jmp         $L549+2Bh (0040d91e)
$L545:
0040D8D5   push        offset string "307" (00422fac)
0040D8DA   call        printf (0040d760)
0040D8DF   add         esp,4
0040D8E2   jmp         $L549+2Bh (0040d91e)
$L547:
0040D8E4   push        offset string "308" (00422fa8)
0040D8E9   call        printf (0040d760)
0040D8EE   add         esp,4
0040D8F1   jmp         $L549+2Bh (0040d91e)
$L549:
0040D8F3   push        offset string "309" (00422fa4)
0040D8F8   call        printf (0040d760)
0040D8FD   add         esp,4
0040D900   jmp         $L549+2Bh (0040d91e)
0040D902   push        offset string "3" (0042210c)
0040D907   call        printf (0040d760)
0040D90C   add         esp,4
0040D90F   jmp         $L549+2Bh (0040d91e)
0040D911   push        offset string "None" (00422f6c)
0040D916   call        printf (0040d760)
0040D91B   add         esp,4
0040D91E   pop         edi
0040D91F   pop         esi
0040D920   pop         ebx
0040D921   add         esp,44h
0040D924   cmp         ebp,esp
0040D926   call        __chkesp (004010a0)
0040D92B   mov         esp,ebp
0040D92D   pop         ebp
0040D92E   ret

Memory:

0040D92F  C6 D8 40 00  曝@.
0040D933  D5 D8 40 00  肇@.
0040D937  E4 D8 40 00  湄@.
0040D93B  F3 D8 40 00  筘@.

小结:

此时的结构称为树型结构,间断大于256时的一种反汇编结构,类似于二分查找

总结

  • switch适用于其值为连续近似的情况,在此情况下switch的性能要高于if多分支,其因为switch利用索引的原理,完成以空间换时间。在不同的情况下,switch的反汇编会有所不同,大致分为四种情况,1.类似于if;2.使用大表结构;3.使用大表+小表结构;4.使用树型结构。

 

 

 
switch语句反汇编

正常switch语句在VC6++的编译器环境下,3个或者3个以下的条件,反汇编语句跟if else if ......没啥区别,也是一个一个比较,看下面例子

源代码

#include "stdafx.h"
#include <string.h>

void fun(int x)
{
switch (x)
{
case 1:
printf("1\n");
break;
case 2:
printf("2\n");
break;
case 3:
printf("3\n");
break;
default:
printf("Error\n");
break;
}
}

int main(int argc, char* argv[])
{
fun(2);
return 0;
}

反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
9: switch (x)
10: {
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
0040103E cmp dword ptr [ebp-4],1
00401042 je fun+32h (00401052)
00401044 cmp dword ptr [ebp-4],2
00401048 je fun+41h (00401061)
0040104A cmp dword ptr [ebp-4],3
0040104E je fun+50h (00401070)
00401050 jmp fun+5Fh (0040107f)
11: case 1:
12: printf("1\n");
00401052 push offset string "1\n" (0042002c)
00401057 call printf (00401110)
0040105C add esp,4
13: break;
0040105F jmp fun+6Ch (0040108c)
14: case 2:
15: printf("2\n");
00401061 push offset string "2\n" (00420028)
00401066 call printf (00401110)
0040106B add esp,4
16: break;
0040106E jmp fun+6Ch (0040108c)
17: case 3:
18: printf("3\n");
00401070 push offset string "3\n" (00420024)
00401075 call printf (00401110)
0040107A add esp,4
19: break;
0040107D jmp fun+6Ch (0040108c)
20: //case 4:
21: // printf("4\n");
22: // break;
23: default:
24: printf("Error\n");
0040107F push offset string "Error\n" (0042001c)
00401084 call printf (00401110)
00401089 add esp,4
25: break;
26: }
27: }
0040108C pop edi
0040108D pop esi
0040108E pop ebx
0040108F add esp,44h
00401092 cmp ebp,esp
00401094 call __chkesp (00401190)
00401099 mov esp,ebp
0040109B pop ebp
0040109C ret


1.添加case后面的值,一个一个增加,观察反汇编代码的变化(何时生成大表).

通过测试,在switch语句添加到含有4个case语句的时候就开始生成大表,且case语句后面的常量是连续的;

源代码

#include "stdafx.h"
#include <string.h>

void fun(int x)
{
switch (x)
{
case 1:
printf("1\n");
break;
case 2:
printf("2\n");
break;
case 3:
printf("3\n");
break;
case 4:
printf("4\n");
break;
default:
printf("Error\n");
break;
}
}

int main(int argc, char* argv[])
{
fun(2);
return 0;
}

反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
0040B800 push ebp
0040B801 mov ebp,esp
0040B803 sub esp,44h
0040B806 push ebx
0040B807 push esi
0040B808 push edi
0040B809 lea edi,[ebp-44h]
0040B80C mov ecx,11h
0040B811 mov eax,0CCCCCCCCh
0040B816 rep stos dword ptr [edi]
9: switch (x)
10: {
0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E mov ecx,dword ptr [ebp-4]
0040B821 sub ecx,1
0040B824 mov dword ptr [ebp-4],ecx
0040B827 cmp dword ptr [ebp-4],3
0040B82B ja $L803+0Fh (0040b873)
0040B82D mov edx,dword ptr [ebp-4]
0040B830 jmp dword ptr [edx*4+40B891h]
11: case 1:
12: printf("1\n");
0040B837 push offset string "1\n" (0042013c)
0040B83C call printf (00401110)
0040B841 add esp,4
13: break;
0040B844 jmp $L803+1Ch (0040b880)
14: case 2:
15: printf("2\n");
0040B846 push offset string "2\n" (0042002c)
0040B84B call printf (00401110)
0040B850 add esp,4
16: break;
0040B853 jmp $L803+1Ch (0040b880)
17: case 3:
18: printf("3\n");
0040B855 push offset string "3\n" (00420028)
0040B85A call printf (00401110)
0040B85F add esp,4
19: break;
0040B862 jmp $L803+1Ch (0040b880)
20: case 4:
21: printf("4\n");
0040B864 push offset string "4\n" (00420024)
0040B869 call printf (00401110)
0040B86E add esp,4
22: break;
0040B871 jmp $L803+1Ch (0040b880)
23: default:
24: printf("Error\n");
0040B873 push offset string "Error\n" (0042001c)
0040B878 call printf (00401110)
0040B87D add esp,4
25: break;
26: }
27: }
0040B880 pop edi
0040B881 pop esi
0040B882 pop ebx
0040B883 add esp,44h
0040B886 cmp ebp,esp
0040B888 call __chkesp (00401190)
0040B88D mov esp,ebp
0040B88F pop ebp
0040B890 ret
0040B891 aaa
0040B892 mov eax,0B8460040h
0040B897 inc eax
0040B898 add byte ptr [ebp-48h],dl
0040B89B inc eax
0040B89C add byte ptr [eax+edi*4+40h],ah
0040B8A0 add ah,cl

对应生成的大表

0040B891 37 B8 40 00
0040B895 46 B8 40 00
0040B899 55 B8 40 00
0040B89D 64 B8 40 00

上述的大表刚好是4个,这也对应了是4个case语句;

对应计算公式的反汇编代码

0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E mov ecx,dword ptr [ebp-4]
0040B821 sub ecx,1
0040B824 mov dword ptr [ebp-4],ecx
0040B827 cmp dword ptr [ebp-4],3
0040B82B ja $L803+0Fh (0040b873)
0040B82D mov edx,dword ptr [ebp-4]
0040B830 jmp dword ptr [edx*4+40B891h]

[edx*4+40B891h] 1x4=4 4+40B891 ----> 刚好就是上面大表对应的0040B895地址,里面的内容就是直达返回结果的内存地址编号0040B846


2.将1中的常量值的顺序打乱,观察反汇编代码(观察顺序是否会影响生成大表).

打乱case语句顺序的情景

源代码
#include "stdafx.h"
#include <string.h>

void fun(int x)
{
switch (x)
{
case 3:
printf("1\n");
break;
case 5:
printf("2\n");
break;
case 7:
printf("3\n");
break;
case 4:
printf("4\n");
break;
case 2:
printf("5\n");
break;
case 1:
printf("6\n");
break;
case 6:
printf("7\n");
break;
case 8:
printf("8\n");
break;
default:
printf("Error\n");
break;
}
}

int main(int argc, char* argv[])
{
fun(2);
return 0;
}


反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
0040B800 push ebp
0040B801 mov ebp,esp
0040B803 sub esp,44h
0040B806 push ebx
0040B807 push esi
0040B808 push edi
0040B809 lea edi,[ebp-44h]
0040B80C mov ecx,11h
0040B811 mov eax,0CCCCCCCCh
0040B816 rep stos dword ptr [edi]
9: switch (x)
10: {
0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E mov ecx,dword ptr [ebp-4]
0040B821 sub ecx,1
0040B824 mov dword ptr [ebp-4],ecx
0040B827 cmp dword ptr [ebp-4],7
0040B82B ja $L811+0Fh (0040b8b3)
0040B831 mov edx,dword ptr [ebp-4]
0040B834 jmp dword ptr [edx*4+40B8D1h]
11: case 3:
12: printf("1\n");
0040B83B push offset string "1\n" (00420f8c)
0040B840 call printf (00401110)
0040B845 add esp,4
13: break;
0040B848 jmp $L811+1Ch (0040b8c0)
14: case 5:
15: printf("2\n");
0040B84A push offset string "2\n" (00420f88)
0040B84F call printf (00401110)
0040B854 add esp,4
16: break;
0040B857 jmp $L811+1Ch (0040b8c0)
17: case 7:
18: printf("3\n");
0040B859 push offset string "3\n" (00420f84)
0040B85E call printf (00401110)
0040B863 add esp,4
19: break;
0040B866 jmp $L811+1Ch (0040b8c0)
20: case 4:
21: printf("4\n");
0040B868 push offset string "4\n" (00420f34)
0040B86D call printf (00401110)
0040B872 add esp,4
22: break;
0040B875 jmp $L811+1Ch (0040b8c0)
23: case 2:
24: printf("5\n");
0040B877 push offset string "5\n" (0042013c)
0040B87C call printf (00401110)
0040B881 add esp,4
25: break;
0040B884 jmp $L811+1Ch (0040b8c0)
26: case 1:
27: printf("6\n");
0040B886 push offset string "6\n" (0042002c)
0040B88B call printf (00401110)
0040B890 add esp,4
28: break;
0040B893 jmp $L811+1Ch (0040b8c0)
29: case 6:
30: printf("7\n");
0040B895 push offset string "7\n" (00420028)
0040B89A call printf (00401110)
0040B89F add esp,4
31: break;
0040B8A2 jmp $L811+1Ch (0040b8c0)
32: case 8:
33: printf("8\n");
0040B8A4 push offset string "8\n" (00420024)
0040B8A9 call printf (00401110)
0040B8AE add esp,4
34: break;
0040B8B1 jmp $L811+1Ch (0040b8c0)
35: default:
36: printf("Error\n");
0040B8B3 push offset string "Error\n" (0042001c)
0040B8B8 call printf (00401110)
0040B8BD add esp,4
37: break;
38: }
39: }
0040B8C0 pop edi
0040B8C1 pop esi
0040B8C2 pop ebx
0040B8C3 add esp,44h
0040B8C6 cmp ebp,esp
0040B8C8 call __chkesp (00401190)
0040B8CD mov esp,ebp
0040B8CF pop ebp
0040B8D0 ret
0040B8D1 xchg bh,byte ptr [eax-4788FFC0h]
0040B8D7 inc eax
0040B8D8 add byte ptr [ebx],bh
0040B8DA mov eax,0B8680040h
0040B8DF inc eax
0040B8E0 add byte ptr [edx-48h],cl
0040B8E3 inc eax
0040B8E4 add byte ptr [ebp+590040B8h],dl
0040B8EA mov eax,0B8A40040h
0040B8EF inc eax
0040B8F0 add ah,cl


对应生成的大表

0040B8D1 86 B8 40 00
0040B8D5 77 B8 40 00
0040B8D9 3B B8 40 00
0040B8DD 68 B8 40 00
0040B8E1 4A B8 40 00
0040B8E5 95 B8 40 00
0040B8E9 59 B8 40 00
0040B8ED A4 B8 40 00


得出结论就是,即使case语句的常量打乱了顺序,仍然不影响大表的生成


3.将case后面的值改成从100开始到109,观察汇编变化(观察值较大时是否生成大表).

源代码

#include "stdafx.h"
#include <string.h>

void fun(int x)
{
switch (x)
{
case 100:
printf("100\n");
break;
case 101:
printf("101\n");
break;
case 102:
printf("102\n");
break;
case 103:
printf("103\n");
break;
case 104:
printf("104\n");
break;
case 105:
printf("105\n");
break;
case 106:
printf("106\n");
break;
case 107:
printf("107\n");
break;
case 108:
printf("108\n");
break;
case 109:
printf("109\n");
break;
default:
printf("Error\n");
break;
}
}

int main(int argc, char* argv[])
{
fun(106);
return 0;
}


反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
0040B800 push ebp
0040B801 mov ebp,esp
0040B803 sub esp,44h
0040B806 push ebx
0040B807 push esi
0040B808 push edi
0040B809 lea edi,[ebp-44h]
0040B80C mov ecx,11h
0040B811 mov eax,0CCCCCCCCh
0040B816 rep stos dword ptr [edi]
9: switch (x)
10: {
0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E mov ecx,dword ptr [ebp-4]
0040B821 sub ecx,64h
0040B824 mov dword ptr [ebp-4],ecx
0040B827 cmp dword ptr [ebp-4],9
0040B82B ja $L815+0Fh (0040b8d7)
0040B831 mov edx,dword ptr [ebp-4]
0040B834 jmp dword ptr [edx*4+40B8F5h]
11: case 100:
12: printf("100\n");
0040B83B push offset string "100\n" (00420fc8)
0040B840 call printf (00401110)
0040B845 add esp,4
13: break;
0040B848 jmp $L815+1Ch (0040b8e4)
14: case 101:
15: printf("101\n");
0040B84D push offset string "101\n" (00420fc0)
0040B852 call printf (00401110)
0040B857 add esp,4
16: break;
0040B85A jmp $L815+1Ch (0040b8e4)
17: case 102:
18: printf("102\n");
0040B85F push offset string "102\n" (00420fb8)
0040B864 call printf (00401110)
0040B869 add esp,4
19: break;
0040B86C jmp $L815+1Ch (0040b8e4)
20: case 103:
21: printf("103\n");
0040B86E push offset string "103\n" (00420f84)
0040B873 call printf (00401110)
0040B878 add esp,4
22: break;
0040B87B jmp $L815+1Ch (0040b8e4)
23: case 104:
24: printf("104\n");
0040B87D push offset string "104\n" (00420fb0)
0040B882 call printf (00401110)
0040B887 add esp,4
25: break;
0040B88A jmp $L815+1Ch (0040b8e4)
26: case 105:
27: printf("105\n");
0040B88C push offset string "105\n" (00420fa8)
0040B891 call printf (00401110)
0040B896 add esp,4
28: break;
0040B899 jmp $L815+1Ch (0040b8e4)
29: case 106:
30: printf("106\n");
0040B89B push offset string "106\n" (00420fa0)
0040B8A0 call printf (00401110)
0040B8A5 add esp,4
31: break;
0040B8A8 jmp $L815+1Ch (0040b8e4)
32: case 107:
33: printf("107\n");
0040B8AA push offset string "107\n" (00420f98)
0040B8AF call printf (00401110)
0040B8B4 add esp,4
34: break;
0040B8B7 jmp $L815+1Ch (0040b8e4)
35: case 108:
36: printf("108\n");
0040B8B9 push offset string "108\n" (00420024)
0040B8BE call printf (00401110)
0040B8C3 add esp,4
37: break;
0040B8C6 jmp $L815+1Ch (0040b8e4)
38: case 109:
39: printf("109\n");
0040B8C8 push offset string "109\n" (00420f90)
0040B8CD call printf (00401110)
0040B8D2 add esp,4
40: break;
0040B8D5 jmp $L815+1Ch (0040b8e4)
41: default:
42: printf("Error\n");
0040B8D7 push offset string "Error\n" (0042001c)
0040B8DC call printf (00401110)
0040B8E1 add esp,4
43: break;
44: }
45: }
0040B8E4 pop edi
0040B8E5 pop esi
0040B8E6 pop ebx
0040B8E7 add esp,44h
0040B8EA cmp ebp,esp
0040B8EC call __chkesp (00401190)
0040B8F1 mov esp,ebp
0040B8F3 pop ebp
0040B8F4 ret
0040B8F5 cmp edi,dword ptr [eax-47B2FFC0h]
0040B8FB inc eax
0040B8FC add byte ptr [edi-48h],bl
0040B8FF inc eax
0040B900 add byte ptr [esi-48h],ch
0040B903 inc eax
0040B904 add byte ptr [ebp-48h],bh
0040B907 inc eax
0040B908 add byte ptr [eax+edi*4-4764FFC0h],cl
0040B90F inc eax
0040B910 add byte ptr [edx-46FFBF48h],ch
0040B916 mov eax,0B8C80040h
0040B91B inc eax
0040B91C add ah,cl

根据大表计算

64 -- 十进制 100
106-100 = 6
edx*4+40B8F5h
从上面计算公式得出
6×4+40B8F5 意思就是0040B8F5所在的大表向下以4个字节移动6位

大表
0040B8F5 3B B8 40 00
0040B8F9 4D B8 40 00
0040B8FD 5F B8 40 00
0040B901 6E B8 40 00
0040B905 7D B8 40 00
0040B909 8C B8 40 00
0040B90D 9B B8 40 00
0040B911 AA B8 40 00
0040B915 B9 B8 40 00
0040B919 C8 B8 40 00

向下以4字节移动6位刚好就是大表地址 0040B90D 9B B8 40 00 对应的内存地址: 0040B89B

30: printf("106\n");
0040B89B push offset string "106\n" (00420fa0)
0040B8A0 call printf (00401110)
0040B8A5 add esp,4

刚好打印106

所以此处结论是依然会生成大表


4.将连续的10项中抹去1项或者2项,观察反汇编有无变化(观察大表空缺位置的处理).

抹去其中case语句 108

#include "stdafx.h"
#include <string.h>

void fun(int x)
{
switch (x)
{
case 100:
printf("100\n");
break;
case 101:
printf("101\n");
break;
case 102:
printf("102\n");
break;
case 103:
printf("103\n");
break;
case 104:
printf("104\n");
break;
case 105:
printf("105\n");
break;
case 106:
printf("106\n");
break;
case 107:
printf("107\n");
break;

case 109:
printf("109\n");
break;
default:
printf("Error\n");
break;
}
}

int main(int argc, char* argv[])
{
fun(106);
return 0;
}


反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
0040B800 push ebp
0040B801 mov ebp,esp
0040B803 sub esp,44h
0040B806 push ebx
0040B807 push esi
0040B808 push edi
0040B809 lea edi,[ebp-44h]
0040B80C mov ecx,11h
0040B811 mov eax,0CCCCCCCCh
0040B816 rep stos dword ptr [edi]
9: switch (x)
10: {
0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E mov ecx,dword ptr [ebp-4]
0040B821 sub ecx,64h
0040B824 mov dword ptr [ebp-4],ecx
0040B827 cmp dword ptr [ebp-4],9
0040B82B ja $L813+0Fh (0040b8c5)
0040B831 mov edx,dword ptr [ebp-4]
0040B834 jmp dword ptr [edx*4+40B8E3h]
11: case 100:
12: printf("100\n");
0040B83B push offset string "100\n" (00420fc0)
0040B840 call printf (00401110)
0040B845 add esp,4
13: break;
0040B848 jmp $L813+1Ch (0040b8d2)
14: case 101:
15: printf("101\n");
0040B84D push offset string "101\n" (00420fb8)
0040B852 call printf (00401110)
0040B857 add esp,4
16: break;
0040B85A jmp $L813+1Ch (0040b8d2)
17: case 102:
18: printf("102\n");
0040B85C push offset string "3\n" (00420f84)
0040B861 call printf (00401110)
0040B866 add esp,4
19: break;
0040B869 jmp $L813+1Ch (0040b8d2)
20: case 103:
21: printf("103\n");
0040B86B push offset string "103\n" (00420fb0)
0040B870 call printf (00401110)
0040B875 add esp,4
22: break;
0040B878 jmp $L813+1Ch (0040b8d2)
23: case 104:
24: printf("104\n");
0040B87A push offset string "104\n" (00420fa8)
0040B87F call printf (00401110)
0040B884 add esp,4
25: break;
0040B887 jmp $L813+1Ch (0040b8d2)
26: case 105:
27: printf("105\n");
0040B889 push offset string "105\n" (00420fa0)
0040B88E call printf (00401110)
0040B893 add esp,4
28: break;
0040B896 jmp $L813+1Ch (0040b8d2)
29: case 106:
30: printf("106\n");
0040B898 push offset string "106\n" (00420f98)
0040B89D call printf (00401110)
0040B8A2 add esp,4
31: break;
0040B8A5 jmp $L813+1Ch (0040b8d2)
32: case 107:
33: printf("107\n");
0040B8A7 push offset string "8\n" (00420024)
0040B8AC call printf (00401110)
0040B8B1 add esp,4
34: break;
0040B8B4 jmp $L813+1Ch (0040b8d2)
35:
36: case 109:
37: printf("109\n");
0040B8B6 push offset string "109\n" (00420f90)
0040B8BB call printf (00401110)
0040B8C0 add esp,4
38: break;
0040B8C3 jmp $L813+1Ch (0040b8d2)
39: default:
40: printf("Error\n");
0040B8C5 push offset string "Error\n" (0042001c)
0040B8CA call printf (00401110)
0040B8CF add esp,4
41: break;
42: }
43: }
0040B8D2 pop edi
0040B8D3 pop esi
0040B8D4 pop ebx
0040B8D5 add esp,44h
0040B8D8 cmp ebp,esp
0040B8DA call __chkesp (00401190)
0040B8DF mov esp,ebp
0040B8E1 pop ebp
0040B8E2 ret
0040B8E3 cmp edi,dword ptr [eax-47B2FFC0h]
0040B8E9 inc eax
0040B8EA add byte ptr [eax+edi*4+40h],bl
0040B8EE add byte ptr [ebx-48h],ch
0040B8F1 inc eax
0040B8F2 add byte ptr [edx-48h],bh
0040B8F5 inc eax
0040B8F6 add byte ptr [ecx-67FFBF48h],cl
0040B8FC mov eax,0B8A70040h
0040B901 inc eax
0040B902 add ch,al
0040B904 mov eax,0B8B60040h
0040B909 inc eax
0040B90A add ah,cl


[edx*4+40B8E3h] 计算公式操作跟上面一样,其中有一个区别就是去掉了一个case语句会将default的对应内存地址放入这个大表中,下面所在位置
0040B903 C5 B8 40 00

大表

0040B8E3 3B B8 40 00
0040B8E7 4D B8 40 00
0040B8EB 5C B8 40 00
0040B8EF 6B B8 40 00
0040B8F3 7A B8 40 00
0040B8F7 89 B8 40 00
0040B8FB 98 B8 40 00
0040B8FF A7 B8 40 00
0040B903 C5 B8 40 00
0040B907 B6 B8 40 00

得出结论:
(1)当抹去1个case语句会将default的内存地址编号放入大表中;
(2)当抹去2个case语句会将default的内存地址编号放入大表中,此时就有两个相同的default内存地址在大表中;
(3)当抹去3个case语句会将default的内存地址编号放入大表中,此时就有三个相同的default内存地址在大表中;
(4)当抹去4个case语句会将default的内存地址编号放入大表中,此时就有四个相同的default内存地址在大表中;
(5)当抹去5个case语句会将default的内存地址编号放入大表中,此时就有五个相同的default内存地址在大表中;
(6)当抹去6个case语句的时候会生成小表;

5.在10项中连续抹去,不要抹去最大值和最小值(观察何时生成小表).

上述4例题中已经论证,当抹去6个case语句的时候会生成小表;

源代码


反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
0040B800 push ebp
0040B801 mov ebp,esp
0040B803 sub esp,44h
0040B806 push ebx
0040B807 push esi
0040B808 push edi
0040B809 lea edi,[ebp-44h]
0040B80C mov ecx,11h
0040B811 mov eax,0CCCCCCCCh
0040B816 rep stos dword ptr [edi]
9: switch (x)
10: {
0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E mov ecx,dword ptr [ebp-4]
0040B821 sub ecx,64h
0040B824 mov dword ptr [ebp-4],ecx
0040B827 cmp dword ptr [ebp-4],9
0040B82B ja $L803+0Fh (0040b87b)
0040B82D mov eax,dword ptr [ebp-4]
0040B830 xor edx,edx
0040B832 mov dl,byte ptr (0040b8ad)[eax]
0040B838 jmp dword ptr [edx*4+40B899h]
11: case 100:
12: printf("100\n");
0040B83F push offset string "100\n" (00420fa0)
0040B844 call printf (00401110)
0040B849 add esp,4
13: break;
0040B84C jmp $L803+1Ch (0040b888)
14: case 101:
15: printf("101\n");
0040B84E push offset string "101\n" (00420f98)
0040B853 call printf (00401110)
0040B858 add esp,4
16: break;
0040B85B jmp $L803+1Ch (0040b888)
17: case 102:
18: printf("102\n");
0040B85D push offset string "104\n" (00420024)
0040B862 call printf (00401110)
0040B867 add esp,4
19: break;
0040B86A jmp $L803+1Ch (0040b888)
20:
21:
22: case 109:
23: printf("109\n");
0040B86C push offset string "109\n" (00420f90)
0040B871 call printf (00401110)
0040B876 add esp,4
24: break;
0040B879 jmp $L803+1Ch (0040b888)
25: default:
26: printf("Error\n");
0040B87B push offset string "Error\n" (0042001c)
0040B880 call printf (00401110)
0040B885 add esp,4
27: break;
28: }
29: }
0040B888 pop edi
0040B889 pop esi
0040B88A pop ebx
0040B88B add esp,44h
0040B88E cmp ebp,esp
0040B890 call __chkesp (00401190)
0040B895 mov esp,ebp
0040B897 pop ebp
0040B898 ret
0040B899 aas
0040B89A mov eax,0B84E0040h
0040B89F inc eax
0040B8A0 add byte ptr [ebp-48h],bl
0040B8A3 inc eax
0040B8A4 add byte ptr [eax+edi*4+40h],ch
0040B8A8 add byte ptr [ebx-48h],bh
0040B8AB inc eax
0040B8AC add byte ptr [eax],al
0040B8AE add dword ptr [edx],eax
0040B8B0 add al,4
0040B8B2 add al,4
0040B8B4 add al,4
0040B8B6 add ecx,esp

对应的大表和小表

0040B899 3F B8 40 00
0040B89D 4E B8 40 00
0040B8A1 5D B8 40 00
0040B8A5 6C B8 40 00
0040B8A9 7B B8 40 00
0040B8AD 00 01 02 04 //小表
0040B8B1 04 04 04 04 //小表
0040B8B5 04 03 CC CC //小表

其中反汇编 0040B832 mov dl,byte ptr (0040b8ad)[eax] 含义

将小表0040B8AD加上eax的值,就是结果,而eax值此时是6,所以向后移动6位即可

0040B8AD 00 01 02 04
0040B8B1 04 04 04 04

00 01 02 04 04 04 04 04 ---> 向后移动6位,就是数6位,得到的结果是04,所以edx的结果是4 根据下面的公式

edx*4+40B899h 4×4+0040B899 可以得出结果是以4字节向后4位即可得到计算结果的内存地址编号:0040B8A9 7B B8 40 00

内存编号对应的内存地址:0040B87B

26: printf("Error\n");
0040B87B push offset string "Error\n" (0042001c)
0040B880 call printf (00401110)
0040B885 add esp,4

6.将case后面常量表达式改成毫不连续的值,观察反汇编变化.

源代码
#include "stdafx.h"
#include <string.h>

void fun(int x)
{
switch (x)
{
case 10:
printf("100\n");
break;
case 201:
printf("101\n");
break;
case 2:
printf("102\n");
break;
case 692:
printf("102\n");
break;
case 532:
printf("102\n");
break;
case 308:
printf("102\n");
break;
case 804:
printf("102\n");
break;
case 749:
printf("102\n");
break;
case 1033:
printf("102\n");
break;

case 29:
printf("109\n");
break;
default:
printf("Error\n");
break;
}
}

int main(int argc, char* argv[])
{
fun(308);
return 0;
}


反汇编代码

4: #include "stdafx.h"
5: #include <string.h>
6:
7: void fun(int x)
8: {
0040B800 push ebp
0040B801 mov ebp,esp
0040B803 sub esp,44h
0040B806 push ebx
0040B807 push esi
0040B808 push edi
0040B809 lea edi,[ebp-44h]
0040B80C mov ecx,11h
0040B811 mov eax,0CCCCCCCCh
0040B816 rep stos dword ptr [edi]
9: switch (x)
10: {
0040B818 mov eax,dword ptr [ebp+8]
0040B81B mov dword ptr [ebp-4],eax
0040B81E cmp dword ptr [ebp-4],214h
0040B825 jg fun+74h (0040b874)
0040B827 cmp dword ptr [ebp-4],214h
0040B82E je fun+0F9h (0040b8f9)
0040B834 cmp dword ptr [ebp-4],1Dh
0040B838 jg fun+59h (0040b859)
0040B83A cmp dword ptr [ebp-4],1Dh
0040B83E je fun+144h (0040b944)
0040B844 cmp dword ptr [ebp-4],2
0040B848 je fun+0DBh (0040b8db)
0040B84E cmp dword ptr [ebp-4],0Ah
0040B852 je fun+0B7h (0040b8b7)
0040B854 jmp fun+153h (0040b953)
0040B859 cmp dword ptr [ebp-4],0C9h
0040B860 je fun+0C9h (0040b8c9)
0040B862 cmp dword ptr [ebp-4],134h
0040B869 je fun+108h (0040b908)
0040B86F jmp fun+153h (0040b953)
0040B874 cmp dword ptr [ebp-4],324h
0040B87B jg fun+0A5h (0040b8a5)
0040B87D cmp dword ptr [ebp-4],324h
0040B884 je fun+117h (0040b917)
0040B88A cmp dword ptr [ebp-4],2B4h
0040B891 je fun+0EAh (0040b8ea)
0040B893 cmp dword ptr [ebp-4],2EDh
0040B89A je fun+126h (0040b926)
0040B8A0 jmp fun+153h (0040b953)
0040B8A5 cmp dword ptr [ebp-4],409h
0040B8AC je fun+135h (0040b935)
0040B8B2 jmp fun+153h (0040b953)
11: case 10:
12: printf("100\n");
0040B8B7 push offset string "100\n" (00420fa0)
0040B8BC call printf (00401110)
0040B8C1 add esp,4
13: break;
0040B8C4 jmp fun+160h (0040b960)
14: case 201:
15: printf("101\n");
0040B8C9 push offset string "101\n" (00420f98)
0040B8CE call printf (00401110)
0040B8D3 add esp,4
16: break;
0040B8D6 jmp fun+160h (0040b960)
17: case 2:
18: printf("102\n");
0040B8DB push offset string "104\n" (00420024)
0040B8E0 call printf (00401110)
0040B8E5 add esp,4
19: break;
0040B8E8 jmp fun+160h (0040b960)
20: case 692:
21: printf("102\n");
0040B8EA push offset string "104\n" (00420024)
0040B8EF call printf (00401110)
0040B8F4 add esp,4
22: break;
0040B8F7 jmp fun+160h (0040b960)
23: case 532:
24: printf("102\n");
0040B8F9 push offset string "104\n" (00420024)
0040B8FE call printf (00401110)
0040B903 add esp,4
25: break;
0040B906 jmp fun+160h (0040b960)
26: case 308:
27: printf("102\n");
0040B908 push offset string "104\n" (00420024)
0040B90D call printf (00401110)
0040B912 add esp,4
28: break;
0040B915 jmp fun+160h (0040b960)
29: case 804:
30: printf("102\n");
0040B917 push offset string "104\n" (00420024)
0040B91C call printf (00401110)
0040B921 add esp,4
31: break;
0040B924 jmp fun+160h (0040b960)
32: case 749:
33: printf("102\n");
0040B926 push offset string "104\n" (00420024)
0040B92B call printf (00401110)
0040B930 add esp,4
34: break;
0040B933 jmp fun+160h (0040b960)
35: case 1033:
36: printf("102\n");
0040B935 push offset string "104\n" (00420024)
0040B93A call printf (00401110)
0040B93F add esp,4
37: break;
0040B942 jmp fun+160h (0040b960)
38:
39: case 29:
40: printf("109\n");
0040B944 push offset string "109\n" (00420f90)
0040B949 call printf (00401110)
0040B94E add esp,4
41: break;
0040B951 jmp fun+160h (0040b960)
42: default:
43: printf("Error\n");
0040B953 push offset string "Error\n" (0042001c)
0040B958 call printf (00401110)
0040B95D add esp,4
44: break;
45: }
46: }
0040B960 pop edi
0040B961 pop esi
0040B962 pop ebx
0040B963 add esp,44h
0040B966 cmp ebp,esp
0040B968 call __chkesp (00401190)
0040B96D mov esp,ebp
0040B96F pop ebp
0040B970 ret


结论:此时switch语句不会生成大表和小表,跟if else if语句判断逻辑基本相同,不能提高效率,如果这样操作就没啥意义;

7.如果switch语句中的case语句常量是无序且每个数字之间大于256,那么这种情况也是无意义的,不能提高效率,这一种极端情况;

 

当分支数量大于3个且部分不连续的时候(差值较大)

 

  1. switch ( argc )
  2. {
  3. case 10:
  4. printf("case 10 ! \r\n");
  5. break;
  6.  
  7. case 11:
  8. printf("case 11 ! \r\n");
  9. break;
  10.  
  11. case 12:
  12. printf("case 12 ! \r\n");
  13. break;
  14.  
  15. case 19:
  16. printf("case 19 ! \r\n");
  17. break;
  18.  
  19. default:
  20. printf("default ! \r\n");
  21. break;
  22. }
  23.  
  24. getchar();

 

反汇编:

 

这个时候我们的两个值的差值是7。这个时候发现又不一样了,寻址方式变成了:movzx eax, byte ptr ds:[ eax + 0xE610A8 ]。dword 变 byte 了,我们数据窗体中看一下。如图选中内容,这就是小表。每一个元素仅仅占1个字节。小表大小也是最大值 19  - 最小值 10。接下来就是 jmp dword ptr ds:[ eax*4
+ 0xE61094 ],这个当然,又是我们亲爱的大表了,联系上下文我们发现。小表里面存的就是大表的下标。为什么要这样设计呢? 由于大表占四个字节,当差距比較大时,生成的大表自然也会变得非常大,这个时候使用小表,能够更加节约内存。

posted @ 2023-04-02 23:02  bonelee  阅读(545)  评论(0编辑  收藏  举报