正向代码:
void main()
{
int a, b;
a = 0;
b = 0;
switch (a+b)
{
case 1: {
printf("11");
break;
}
default:
break;
}
对应的汇编代码:
int a, b;
a = 0;
00BC1838 mov dword ptr [a],0
b = 0;
00BC183F mov dword ptr [b],0
switch (a+b)
00BC1846 mov eax,dword ptr [a]
00BC1849 add eax,dword ptr [b]
00BC184C mov dword ptr [ebp-0DCh],eax
00BC1852 cmp dword ptr [ebp-0DCh],1
00BC1859 je main+4Dh (0BC185Dh)
00BC185B jmp main+5Ah (0BC186Ah)
{
case 1: {
printf("11");
00BC185D push offset string "11" (0BC7B30h)
00BC1862 call _printf (0BC104Bh)
00BC1867 add esp,4
break;
}
default:
break;
}
}
00BC186A xor eax,eax
原函数中只有两个局部变量,在汇编代码可以看到编译器生产的第三个变量ebp-DC ,用来保存Switch中表达式的值。
Switch…case语句在汇编中的表示一般有四种情况:
分支个数在3个及以下的情况:
在上面基础上多加入两个分支看看汇编代码,
switch (a+b)
000F4356 mov eax,dword ptr [a]
000F4359 add eax,dword ptr [b]
000F435C mov dword ptr [ebp-0DCh],eax
000F4362 cmp dword ptr [ebp-0DCh],1
000F4369 je main+5Fh (0F437Fh)
000F436B cmp dword ptr [ebp-0DCh],2
000F4372 je main+6Eh (0F438Eh)
000F4374 cmp dword ptr [ebp-0DCh],3
000F437B je main+7Dh (0F439Dh)
000F437D jmp main+8Ah (0F43AAh)
{
case 1: {
printf("11");
000F437F push offset string "11" (0F7B30h)
000F4384 call _printf (0F104Bh)
000F4389 add esp,4
break;
000F438C jmp main+8Ah (0F43AAh)
}
case 2: {
printf("11");
000F438E push offset string "11" (0F7B30h)
000F4393 call _printf (0F104Bh)
000F4398 add esp,4
break;
000F439B jmp main+8Ah (0F43AAh)
}
case 3: {
printf("11");
000F439D push offset string "11" (0F7B30h)
000F43A2 call _printf (0F104Bh)
000F43A7 add esp,4
break;
}
default:
break;
}
用cmp指令判断表达式值是否与case后面的相等,如相等就跳到对应的代码,全都不相等就通过Jmp跳到default处。
四条及以上连续分支的情况:
当有四条分支时,汇编代码就发生了变化:
int a, b;
a = 0;
00534348 mov dword ptr [a],0
b = 0;
0053434F mov dword ptr [b],0
switch (a+b)
00534356 mov eax,dword ptr [a]
00534359 add eax,dword ptr [b]
0053435C mov dword ptr [ebp-0DCh],eax
00534362 mov ecx,dword ptr [ebp-0DCh]
00534368 sub ecx,1
0053436B mov dword ptr [ebp-0DCh],ecx
00534371 cmp dword ptr [ebp-0DCh],3
00534378 ja $LN7+0Dh (05343C1h)
0053437A mov edx,dword ptr [ebp-0DCh]
00534380 jmp dword ptr [edx*4+5343D8h]
{
case 1: {
printf("11");
00534387 push offset string "11" (0537B30h)
0053438C call _printf (053104Bh)
00534391 add esp,4
break;
00534394 jmp $LN7+0Dh (05343C1h)
}
case 2: {
printf("11");
00534396 push offset string "11" (0537B30h)
0053439B call _printf (053104Bh)
005343A0 add esp,4
break;
005343A3 jmp $LN7+0Dh (05343C1h)
}
case 3: {
printf("11");
005343A5 push offset string "11" (0537B30h)
005343AA call _printf (053104Bh)
005343AF add esp,4
break;
005343B2 jmp $LN7+0Dh (05343C1h)
}
case 4: {
printf("11");
005343B4 push offset string "11" (0537B30h)
005343B9 call _printf (053104Bh)
005343BE add esp,4
break;
}
default:
break;
}
}
005343C1 xor eax,eax
局部变量ebp-0DC保存Switch表达式的值,然后用sub指令将ebp-0DCh值-1,ebp-0DCh在这里起索引的作用,减一后索引从0开始。由于下面case后面跟着的值是连续的,这里就直接用cmp判断switch中的值是否超过最大索引,超过则直接跳转到default处,如果再索引范围内,就通过
jmp dword ptr [edx*4+5343D8h]
跳转到对应的位置,这里[5343D8]保存的是一张跳转地址的表。
四条及以上不连续分支的情况1:
以上是在case后面的值连续的情况下,那么接下去看下值不连续的情况:
int a, b;
a = 0;
00C14348 mov dword ptr [a],0
b = 0;
00C1434F mov dword ptr [b],0
switch (a+b)
00C14356 mov eax,dword ptr [a]
00C14359 add eax,dword ptr [b]
00C1435C mov dword ptr [ebp-0DCh],eax
00C14362 mov ecx,dword ptr [ebp-0DCh]
00C14368 sub ecx,1
00C1436B mov dword ptr [ebp-0DCh],ecx
00C14371 cmp dword ptr [ebp-0DCh],5
00C14378 ja $LN7+0Dh (0C143C1h)
00C1437A mov edx,dword ptr [ebp-0DCh]
00C14380 jmp dword ptr [edx*4+0C143D8h]
{
case 1: {
printf("11");
00C14387 push offset string "11" (0C17B30h)
00C1438C call _printf (0C1104Bh)
00C14391 add esp,4
break;
00C14394 jmp $LN7+0Dh (0C143C1h)
}
case 2: {
printf("11");
00C14396 push offset string "11" (0C17B30h)
00C1439B call _printf (0C1104Bh)
00C143A0 add esp,4
break;
00C143A3 jmp $LN7+0Dh (0C143C1h)
}
case 3: {
printf("11");
00C143A5 push offset string "11" (0C17B30h)
00C143AA call _printf (0C1104Bh)
00C143AF add esp,4
break;
00C143B2 jmp $LN7+0Dh (0C143C1h)
}
case 6: {
printf("11");
00C143B4 push offset string "11" (0C17B30h)
00C143B9 call _printf (0C1104Bh)
00C143BE add esp,4
break;
}
default:
break;
}
}
00C143C1 xor eax,eax
汇编代码与上面相比没有什么变化,看下地址跳转[0C143D8]中保存的值
0x00C143D8 87 43 c1 00 ?C?.
0x00C143DC 96 43 c1 00 ?C?.
0x00C143E0 a5 43 c1 00 ?C?.
0x00C143E4 c1 43 c1 00 ?C?.
0x00C143E8 c1 43 c1 00 ?C?.
0x00C143EC b4 43 c1 00 ?C?.
前三个对应的case 1,case2,case 3的地址,最后一个对应的case 6后面地址,我们并没有写case 4 和case 5,那么跳转表中保存的两个相同的地址实际上就是default的地址。
接下来看另一种情况:
正向代码:
void main()
{
int a, b;
a = 0;
b = 0;
switch (a+b)
{
case 100: {
printf("11");
break;
}
case 200: {
printf("11");
break;
}
case 300: {
printf("11");
break;
}
case 400: {
printf("11");
break;
}
case 1000: {
printf("11");
break;
}
default:
break;
}
}
四条及以上不连续分支的情况2:
以上我们知道在case后面的值不连续时编译器会用default的地址填充不存在的索引的跳转地址,使其连续。但是如果相差很多的话编译器还会逐条填充吗?
正向代码:
switch (a+b)
{
case 1: {
printf("11");
break;
}
case 2: {
printf("11");
break;
}
case 3: {
printf("11");
break;
}
case 10: {
printf("11");
break;
}
default:
break;
}
这里的3和10次相差的蛮多的,看下此时的汇编代码:
switch (a+b)
00A54E86 mov eax,dword ptr [a]
00A54E89 add eax,dword ptr [b]
00A54E8C mov dword ptr [ebp-0DCh],eax
00A54E92 mov ecx,dword ptr [ebp-0DCh]
00A54E98 sub ecx,1
00A54E9B mov dword ptr [ebp-0DCh],ecx
00A54EA1 cmp dword ptr [ebp-0DCh],9
00A54EA8 ja $LN7+0Dh (0A54EF8h)
00A54EAA mov edx,dword ptr [ebp-0DCh]
00A54EB0 movzx eax,byte ptr [edx+0A54F24h]
00A54EB7 jmp dword ptr [eax*4+0A54F10h]
{
case 1: {
printf("11");
00A54EBE push offset string "11" (0A57B30h)
00A54EC3 call _printf (0A5104Bh)
00A54EC8 add esp,4
break;
00A54ECB jmp $LN7+0Dh (0A54EF8h)
}
case 2: {
printf("11");
00A54ECD push offset string "11" (0A57B30h)
00A54ED2 call _printf (0A5104Bh)
00A54ED7 add esp,4
break;
00A54EDA jmp $LN7+0Dh (0A54EF8h)
}
case 3: {
printf("11");
00A54EDC push offset string "11" (0A57B30h)
00A54EE1 call _printf (0A5104Bh)
00A54EE6 add esp,4
break;
00A54EE9 jmp $LN7+0Dh (0A54EF8h)
}
case 10: {
printf("11");
00A54EEB push offset string "11" (0A57B30h)
00A54EF0 call _printf (0A5104Bh)
00A54EF5 add esp,4
break;
}
default:
break;
}
}
00A54EF8 xor eax,eax
新多出了这两句:
00A54EB0 movzx eax,byte ptr [edx+0A54F24h]
00A54EB7 jmp dword ptr [eax*4+0A54F10h]
用到了eax,这里多了0A54F24h这张表,查看一下:
0x00A54F24 00 01 02 04 ....
0x00A54F28 04 04 04 04 ....
0x00A54F2C 04 03 cc cc ..??
0x00A54F30 cc cc cc cc ????
0x00A54F34 cc cc cc cc
0 1 2对应的是case 1 ,case 2,case 3的索引, 然是6个04 ,再是 3
我们发现这张多出来的表是一张保存着索引值得表,其中不存在得case 4 --case 9的索引都替换为default的索引 也就是4,对比上一种情况,替换地址需要4个字节,而替换索引只需要1个字节,引入一张索引的表更加合理。
case后面的值相差很多情况:
几个case后面的值相差很多,
此时的汇编代码:
00184E7F mov dword ptr [b],0
switch (a+b)
00184E86 mov eax,dword ptr [a]
00184E89 add eax,dword ptr [b]
00184E8C mov dword ptr [ebp-0DCh],eax
00184E92 cmp dword ptr [ebp-0DCh],12Ch
00184E9C jg main+71h (0184EC1h)
00184E9E cmp dword ptr [ebp-0DCh],12Ch
00184EA8 je main+0A9h (0184EF9h)
00184EAA cmp dword ptr [ebp-0DCh],64h
00184EB1 je main+8Bh (0184EDBh)
00184EB3 cmp dword ptr [ebp-0DCh],0C8h
00184EBD je main+9Ah (0184EEAh)
00184EBF jmp main+0D4h (0184F24h)
00184EC1 cmp dword ptr [ebp-0DCh],190h
00184ECB je main+0B8h (0184F08h)
00184ECD cmp dword ptr [ebp-0DCh],3E8h
00184ED7 je main+0C7h (0184F17h)
00184ED9 jmp main+0D4h (0184F24h)
{
case 100: {
printf("11");
00184EDB push offset string "11" (0187B30h)
00184EE0 call _printf (018104Bh)
00184EE5 add esp,4
break;
00184EE8 jmp main+0D4h (0184F24h)
}
case 200: {
printf("11");
00184EEA push offset string "11" (0187B30h)
00184EEF call _printf (018104Bh)
00184EF4 add esp,4
break;
00184EF7 jmp main+0D4h (0184F24h)
}
case 300: {
printf("11");
00184EF9 push offset string "11" (0187B30h)
00184EFE call _printf (018104Bh)
00184F03 add esp,4
break;
00184F06 jmp main+0D4h (0184F24h)
}
case 400: {
printf("11");
00184F08 push offset string "11" (0187B30h)
00184F0D call _printf (018104Bh)
00184F12 add esp,4
break;
00184F15 jmp main+0D4h (0184F24h)
}
case 1000: {
printf("11");
00184F17 push offset string "11" (0187B30h)
}
case 1000: {
printf("11");
00184F1C call _printf (018104Bh)
00184F21 add esp,4
break;
}
default:
break;
}
}
00184F24 xor eax,eax
00184E92 cmp dword ptr [ebp-0DCh],12Ch
00184E9C jg main+71h (0184EC1h) 这两句先判断表达式是否大于300,这里的300既不是最大值也不是最小值,而是中间值,采用二分法,如果大于300就继续与400和1000比较,小于300就和100,200比较。