在C/C++中,我们有三种循环结构。分别是while,do-while,for。下面的代码中,三种循环结构所做的事情是一样,通过比较这三种情况下的生成的汇编,我们就可以分析得出三种循环结构在内存中的表现形式得不同点。
int main()
{
int count = 0;
count = 0;
while (count < 10)
{
printf("while...\n");
count++;
}
printf("while over\n");
count = 0;
do
{
printf("do while...\n");
count++;
} while (count < 10);
printf("do while over\n");
for (count = 0; count < 10; count++)
{
printf("for...\n");
}
printf("for over\n");
return 0;
}
一.while循环
count = 0;
00E110EF mov dword ptr [count],0 //为count赋值为0
while (count < 10)
00E110F6 cmp dword ptr [count],0Ah //将count得值与0xA也就是10做比较
00E110FA jge main+54h (0E11114h) //若大于等于则跳出循环
{
printf("while...\n");
00E110FC push offset string "while...\n" (0E831B0h) //符合条件执行循环语句内容
00E11101 call printf (0E111A0h)
00E11106 add esp,4
count++;
00E11109 mov eax,dword ptr [count] //把count值赋给eax
00E1110C add eax,1 //eax加1
00E1110F mov dword ptr [count],eax //在把eax赋值给count实现count++
}
00E11112 jmp main+36h (0E110F6h) //跳转到count与0xA比较得地方
printf("while over\n");
00E11114 push offset string "while over\n" (0E831BCh) //while循环之外得代码
00E11119 call printf (0E111A0h)
00E1111E add esp,4
由此可以看到while循环中,我们先是判断是否满足条件,不满足得话会跳出循环,如果满足执行相应得指令以后在跳转到判断语句处进行执行。执行得流程图如下图所示:
二.do-while循环
count = 0;
00E11121 mov dword ptr [count],0 //为count赋值为0
do
{
printf("do while...\n");
00E11128 push offset string "do while...\n" (0E831C8h) //执行循环条件成立的代码
00E1112D call printf (0E111A0h)
00E11132 add esp,4
count++;
00E11135 mov eax,dword ptr [count]
00E11138 add eax,1
00E1113B mov dword ptr [count],eax //实现count+1的操作
} while (count < 10);
00E1113E cmp dword ptr [count],0Ah //将count与10做比较
00E11142 jl main+68h (0E11128h) //如果小于10则跳转到循环开始处
printf("do while over\n");
00E11144 push offset string "do while over\n" (0E831D8h)
00E11149 call printf (0E111A0h)
00E1114E add esp,4
可以看到do-while循环中,我们先是执行循环条件成立时候的代码,然后在进行判断,如果符合条件则跳转回去继续执行。流程图如下
三.for循环
for (count = 0; count < 10; count++)
00E11151 mov dword ptr [count],0 //为count赋值为0
00E11158 jmp main+0A3h (0E11163h) //跳转到判断语句执行
00E1115A mov eax,dword ptr [count]
00E1115D add eax,1
00E11160 mov dword ptr [count],eax //将count的值+1
00E11163 cmp dword ptr [count],0Ah //将count的值与10做比较
00E11167 jge main+0B8h (0E11178h) //如果大于则跳转到循环体外
{
printf("for...\n");
00E11169 push offset string "for...\n" (0E831E8h) //执行条件成立的代码
00E1116E call printf (0E111A0h)
00E11173 add esp,4
}
00E11176 jmp main+9Ah (0E1115Ah) //跳转到count值加1的代码执行
printf("for over\n");
00E11178 push offset string "for over\n" (0E831F0h)
00E1117D call printf (0E111A0h)
00E11182 add esp,4
在for循环中,先执行第一个分号前的赋值指令,然后就跳转到判断指令进行判断,如果满足条件则执行条件成立指令执行随后在跳转到第二个分号后的语句执行,这个语句继续向下执行会继续判断条件是否成立。流程图如下
四.for与数组及指针
由于for循环经常和数组指针一起使用,所以下面给出一个例子来看看汇编中的表现形式
int arrTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
00F910E8 mov dword ptr [arrTest],0
00F910EF mov dword ptr [ebp-28h],1
00F910F6 mov dword ptr [ebp-24h],2
00F910FD mov dword ptr [ebp-20h],3
00F91104 mov dword ptr [ebp-1Ch],4
00F9110B mov dword ptr [ebp-18h],5
00F91112 mov dword ptr [ebp-14h],6
00F91119 mov dword ptr [ebp-10h],7
00F91120 mov dword ptr [ebp-0Ch],8
00F91127 mov dword ptr [ebp-8],9 //为数组进行初始化赋值
int *pTest = arrTest;
00F9112E lea eax,[arrTest]
00F91131 mov dword ptr [pTest],eax //将数组地址赋值给指针
for (int i = 0; i < 10; i++)
00F91134 mov dword ptr [ebp-44h],0 //对i进行初始化,此时地址ebp-0x44所保存的就是i的值
00F9113B jmp main+86h (0F91146h) //跳转到条件判断
00F9113D mov eax,dword ptr [ebp-44h]
00F91140 add eax,1
00F91143 mov dword ptr [ebp-44h],eax //对i进行加1操作
00F91146 cmp dword ptr [ebp-44h],0Ah //判断i是否大于10
00F9114A jge main+0ADh (0F9116Dh) //大于10则跳出循环
{
printf("arrTest[i]=%d,*(pTest+i)=%d\n", arrTest[i], *(pTest + i));
00F9114C mov eax,dword ptr [ebp-44h] //取出i的值赋给eax
00F9114F mov ecx,dword ptr [pTest] //取出pTest的值,此时这个值是arrTest的首地址
00F91152 mov edx,dword ptr [ecx+eax*4] //根据首地址与i*4的偏移得到要用到的数的地址
00F91155 push edx
00F91156 mov eax,dword ptr [ebp-44h] //得到i的值
00F91159 mov ecx,dword ptr [arrTest+eax*4] //根据i的值与数组首地址从数组中取出数据
00F9115D push ecx
00F9115E push offset string "arrTest[i]=%d,*(pTest+i)=%d\n" (010031B0h)
00F91163 call printf (0F911B0h)
00F91168 add esp,0Ch
}
00F9116B jmp main+7Dh (0F9113Dh) //跳转到i++的语句执行
由上可知,数组和指针的差别不大,只不过指针运算会多一次取出地址的操作。另外由于这里没用对下标进行判断是否超过了数组的界限,只是根据下标算偏移而得出地址,所以对于新手来说往往会犯的一个错误就是写出了如下的代码导致了程序运行错误。
int main()
{
int i = 0;
int arrTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (i = 1; i <= 10; i++)
{
arrTest[i] = 0;
}
return 0;
}
这段代码本身是想给arrTest里面的元素赋值为0。但是因为for循环的时候没有用对下标导致了访问了数组之外的地址,而这个地址刚好是i的地址,导致当i等于10的时候,为arrTest[i]赋值的时候就是给i赋值。这就导致了程序的无限循环。反汇编结果如下:
6: int i = 0;
00401028 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 //为i赋值为1,此时ebp-4就是i的地址
7: int arrTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
0040102F C7 45 D4 00 00 00 00 mov dword ptr [ebp-2Ch],0
00401036 C7 45 D8 01 00 00 00 mov dword ptr [ebp-28h],1
0040103D C7 45 DC 02 00 00 00 mov dword ptr [ebp-24h],2
00401044 C7 45 E0 03 00 00 00 mov dword ptr [ebp-20h],3
0040104B C7 45 E4 04 00 00 00 mov dword ptr [ebp-1Ch],4
00401052 C7 45 E8 05 00 00 00 mov dword ptr [ebp-18h],5
00401059 C7 45 EC 06 00 00 00 mov dword ptr [ebp-14h],6
00401060 C7 45 F0 07 00 00 00 mov dword ptr [ebp-10h],7
00401067 C7 45 F4 08 00 00 00 mov dword ptr [ebp-0Ch],8
0040106E C7 45 F8 09 00 00 00 mov dword ptr [ebp-8],9 //为数组初始化,此时ebp-0x2C就是数组首地址
8:
9: for (i = 1; i <= 10; i++)
00401075 C7 45 FC 01 00 00 00 mov dword ptr [ebp-4],1 //为i赋值为1
0040107C EB 09 jmp main+77h (00401087) //跳转到比较指令
0040107E 8B 45 FC mov eax,dword ptr [ebp-4]
00401081 83 C0 01 add eax,1
00401084 89 45 FC mov dword ptr [ebp-4],eax //实现i++
00401087 83 7D FC 0A cmp dword ptr [ebp-4],0Ah //判断是否大于10
0040108B 7F 0D jg main+8Ah (0040109a) //大于则跳出循环结构
10: {
11: arrTest[i] = 0;
0040108D 8B 4D FC mov ecx,dword ptr [ebp-4] //取出i的地址
00401090 C7 44 8D D4 00 00 00 mov dword ptr [ebp+ecx*4-2Ch],0 //根据数组首地址与ecx*4的值算出地址赋值为0
12: }
00401098 EB E4 jmp main+6Eh (0040107e) //跳到i++的指令执行
当i等于10的时候我们可以看看ebp-4与ebp+ecx*4-0x2C的值。
可以看到两者的值是相等的,也就是说执行
mov dword ptr [ebp+ecx*4-2Ch],0
和执行
mov dword ptr [ebp-4],0
是相同的,而ebp-4此时正是i的地址,i被赋值为0,这就导致了无线循环。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2021-10-20 11:19
被1900编辑
,原因: