Windows逆向安全(一)之基础知识(九)
汇编比较三种循环
众所周知,在C语言可以使用可以使用三种循环,分别是:while、do…while和for
本文从汇编的角度出发,观察这三种循环的差异
范例代码
先贴出三种循环的代码,分别用这三种循环计算
0+1+2+3+4+5+6+7+8+9(从0一直加到9)
#include "stdafx.h"
int loop1(){
int i=0,j=0;
for(i=0;i<10;i++){
j=j+i;
}
return j;
}
int loop2(){
int i=0,j=0;
while(i<10){
j=j+i;
i=i+1;
}
return j;
}
int loop3(){
int i=0,j=0;
do {
j=j+i;
i=i+1;
} while(i<10);
return j;
}
int main(int argc, char* argv[])
{
int result=0;
result=loop1();
printf("%d\n",result);
result=loop2();
printf("%d\n",result);
result=loop3();
printf("%d\n",result);
return 0;
}
运行结果
显然,这三种循环都能正确地计算出结果,接下来挨个分析这三种循环的汇编代码
for循环
省去汇编代码中保护现场、提升堆栈、初始化堆栈、恢复现场和返回的代码,直接看代码的对应汇编
9: int i=0,j=0;
0040D748 mov dword ptr [ebp-4],0
0040D74F mov dword ptr [ebp-8],0
10: for(i=0;i<10;i++){
0040D756 mov dword ptr [ebp-4],0
0040D75D jmp loop1+38h (0040d768)
0040D75F mov eax,dword ptr [ebp-4]
0040D762 add eax,1
0040D765 mov dword ptr [ebp-4],eax
0040D768 cmp dword ptr [ebp-4],0Ah
0040D76C jge loop1+49h (0040d779)
11: j=j+i;
0040D76E mov ecx,dword ptr [ebp-8]
0040D771 add ecx,dword ptr [ebp-4]
0040D774 mov dword ptr [ebp-8],ecx
12: }
0040D777 jmp loop1+2Fh (0040d75f)
13: return j;
0040D779 mov eax,dword ptr [ebp-8]
14: }
头两行汇编对应将i和j初始化为0,没什么好说的
9: int i=0,j=0;
0040D748 mov dword ptr [ebp-4],0
0040D74F mov dword ptr [ebp-8],0
接下来看循环语句部分:
第一行为:
0040D756 mov dword ptr [ebp-4],0
对应for(i=0;i<10;i++)中的i=0
接下来是一个绝对跳转指令:
0040D75D jmp loop1+38h (0040d768)
跳转的地址为:0040d768
0040D768 cmp dword ptr [ebp-4],0Ah
0040D76C jge loop1+49h (0040d779)
这里就是比较 i 和0Ah(十六进制的10),也就是对应for(i=0;i<10;i++)中的i<10
jge指令:jump greater equal,当大于等于时跳转(有符号)
所以这里就是判断i是否大于10,如果大于10就跳转到0040d779
接下来看0040d779对应的代码:
13: return j;
0040D779 mov eax,dword ptr [ebp-8]
这里就是返回的语句了,已经在循环的外部了,即上面的跳转其实就是退出循环的语句
如果前面的i<10,则继续向下看汇编语句:
11: j=j+i;
0040D76E mov ecx,dword ptr [ebp-8]
0040D771 add ecx,dword ptr [ebp-4]
0040D774 mov dword ptr [ebp-8],ecx
先将j赋值给ecx,然后用ecx加上i,最后将ecx赋值给j,即j=j+i,也就是我们循环里要执行的内容
执行完上面的j=j+1后,下一行指令是一个绝对跳转:
12: }
0040D777 jmp loop1+2Fh (0040d75f)
跳转的地址为0040d75f,继续观察:
0040D75F mov eax,dword ptr [ebp-4]
0040D762 add eax,1
0040D765 mov dword ptr [ebp-4],eax
0040D768 cmp dword ptr [ebp-4],0Ah
先将i的值赋给eax,然后eax加一后把eax赋给i,对应for(i=0;i<10;i++)中的i++
然后就回到了之前的步骤,比较i是否大于等于10,是则退出循环,否则继续循环执行
for循环总结
通过前面的分析大致可以知道for循环的执行流程为:
while循环
省去汇编代码中保护现场、提升堆栈、初始化堆栈、恢复现场和返回的代码,直接看代码的对应汇编
17: int i=0,j=0;
0040D7A8 mov dword ptr [ebp-4],0
0040D7AF mov dword ptr [ebp-8],0
18: while(i<10){
0040D7B6 cmp dword ptr [ebp-4],0Ah
0040D7BA jge loop2+40h (0040d7d0)
19: j=j+i;
0040D7BC mov eax,dword ptr [ebp-8]
0040D7BF add eax,dword ptr [ebp-4]
0040D7C2 mov dword ptr [ebp-8],eax
20: i=i+1;
0040D7C5 mov ecx,dword ptr [ebp-4]
0040D7C8 add ecx,1
0040D7CB mov dword ptr [ebp-4],ecx
21: }
0040D7CE jmp loop2+26h (0040d7b6)
22: return j;
0040D7D0 mov eax,dword ptr [ebp-8]
23: }
头两行初始化i和j,和前面一样,不属于循环的内容,这里给出i和j对应的地址
下面正式来看循环的内容:
18: while(i<10){
0040D7B6 cmp dword ptr [ebp-4],0Ah
0040D7BA jge loop2+40h (0040d7d0)
比较 i 和 10
jge:jump greater equal,大于等于就跳转(有符号)
综上两句就是用来判断 i<10 对应while(i<10)中的 判断条件 i<10
如果i>=10,则跳转到0040d7d0,退出循环
22: return j;
0040D7D0 mov eax,dword ptr [ebp-8]
这里就是返回的语句了,已经在循环的外部了
如果i<10,也就是没有跳转,接着看下面的语句:
19: j=j+i;
0040D7BC mov eax,dword ptr [ebp-8]
0040D7BF add eax,dword ptr [ebp-4]
0040D7C2 mov dword ptr [ebp-8],eax
20: i=i+1;
0040D7C5 mov ecx,dword ptr [ebp-4]
0040D7C8 add ecx,1
0040D7CB mov dword ptr [ebp-4],ecx
21: }
就是执行我们while里面所写的代码(执行循环内的代码)
接着看下面的语句,是一个无条件跳转,跳转到前面的0040d7b6
0040D7CE jmp loop2+26h (0040d7b6)
来看看0040d7b6,就是最开始的判断语句,如果i<10则继续执行,否则跳出循环
0040D7B6 cmp dword ptr [ebp-4],0Ah
0040D7BA jge loop2+40h (0040d7d0)
while循环总结
通过前面的分析大致可以知道while循环的执行流程为
do while循环
省去汇编代码中保护现场、提升堆栈、初始化堆栈、恢复现场和返回的代码,直接看代码的对应汇编
25: int i=0,j=0;
0040D888 mov dword ptr [ebp-4],0
0040D88F mov dword ptr [ebp-8],0
26: do {
27: j=j+i;
0040D896 mov eax,dword ptr [ebp-8]
0040D899 add eax,dword ptr [ebp-4]
0040D89C mov dword ptr [ebp-8],eax
28: i=i+1;
0040D89F mov ecx,dword ptr [ebp-4]
0040D8A2 add ecx,1
0040D8A5 mov dword ptr [ebp-4],ecx
29: } while(i<10);
0040D8A8 cmp dword ptr [ebp-4],0Ah
0040D8AC jl loop3+26h (0040d896)
30: return j;
0040D8AE mov eax,dword ptr [ebp-8]
31: }
头两行初始化i和j,和前面一样,不属于循环的内容,这里给出i和j对应的地址
下面正式来看循环的内容:
26: do {
27: j=j+i;
0040D896 mov eax,dword ptr [ebp-8]
0040D899 add eax,dword ptr [ebp-4]
0040D89C mov dword ptr [ebp-8],eax
28: i=i+1;
0040D89F mov ecx,dword ptr [ebp-4]
0040D8A2 add ecx,1
0040D8A5 mov dword ptr [ebp-4],ecx
直接执行我们do while里面所写的代码(执行循环内的代码)
接着看下面的代码:
29: } while(i<10);
0040D8A8 cmp dword ptr [ebp-4],0Ah
0040D8AC jl loop3+26h (0040d896)
先是比较 i 和 10
然后 jl :jump less (有符号数),当i<10的时候才跳转
跳转地址为0040d896,也就是前面的代码
26: do {
27: j=j+i;
0040D896 mov eax,dword ptr [ebp-8]
如果没有跳转则执行下面的代码
30: return j;
0040D8AE mov eax,dword ptr [ebp-8]
注意到这里已经是退出循环的状态了,返回j
do while循环总结
过前面的分析大致可以知道do while循环的执行流程为:
比较
从流程图就不难看出,三种循环的复杂程度:
for循环>while循环>do while循环
因此执行效率则是:
do while循环>while循环>for循环
- for循环是先判断条件是否不满足i<10,也就是是否满足i>=10,不满足条件则跳出循环并返回;满足条件则执行循环内的代码,执行完循环内代码后无条件跳转到计数增加i=i+1再回到判断条件
- while循环也是先判断条件是否不满足i<10,也就是是否满足i>=10,不满足条件则跳出循环并返回;满足条件则执行循环内的代码,执行完循环内代码后无条件跳转到判断条件处
- do while循环则是先执行循环内的语句,然后再判断条件,判断条件是否满足i<10,满足条件则跳回去继续执行循环内的代码