7.
002718E2call002710B4002718E7addesp,8
call指令是调用的意思,这里我们需要将call指令的下一条指令的地址进行压栈,这里因为函数调用会返回,而返回的地址正是call指令的下一条地址
接下来就正是进入我们的Add函数了
Add函数汇编代码:
intAdd(intx, inty) { 00271770pushebp00271771movebp,esp00271773subesp,0CCh00271779pushebx0027177Apushesi0027177Bpushedi0027177Cleaedi,[ebp-0Ch] 0027177Fmovecx,300271784moveax,0CCCCCCCCh00271789repstosdwordptres:[edi] 0027178Bmovecx,27C003h00271790call0027131Bintz=0; 00271795movdwordptr [ebp-8],0z=x+y; 0027179Cmoveax,dwordptr [ebp+8] 0027179Faddeax,dwordptr [ebp+0Ch] 002717A2movdwordptr [ebp-8],eaxreturnz; 002717A5moveax,dwordptr [ebp-8] } 002717A8popedi002717A9popesi002717AApopebx002717ABaddesp,0CCh002717B1cmpebp,esp002717B3call00271244002717B8movesp,ebp002717BApopebp002717BBret
8.
00271770pushebp00271771movebp,esp00271773subesp,0CCh00271779pushebx0027177Apushesi0027177Bpushedi0027177Cleaedi,[ebp-0Ch] 0027177Fmovecx,300271784moveax,0CCCCCCCCh00271789repstosdwordptres:[edi]
这里就进入了我们Add函数的汇编指令了,大家有没有发现这串代码和前面main函数开辟函数栈帧的代码很相似:
push:首先是ebp压栈,
mov:移动,将esp的值赋给edp
sub:将esp的值减去0Ch大小的空间
将edi向下的39这么大的空间里全部赋值为cccccccc
9.
intz=0; 00271795movdwordptr [ebp-8],0z=x+y; 0027179Cmoveax,dwordptr [ebp+8] 0027179Faddeax,dwordptr [ebp+0Ch] 002717A2movdwordptr [ebp-8],eaxreturnz; 002717A5moveax,dwordptr [ebp-8]
00271795movdwordptr [ebp-8],0
首先将[ebp-8]位置赋值为0给变量z
0027179Cmoveax,dwordptr [ebp+8] 0027179Faddeax,dwordptr [ebp+0Ch] 002717A2movdwordptr [ebp-8],eax
接下来将[ebp+8]位置的值赋给eax,而此时[ebp+8]正是我们在上面创建好的a变量10,即:eax=10,接着执行add,将[ebp+0ch]的值加给eax,而[ebp+0ch]的值正是我们在上面创建好的b变量20,此时eax=30,接着继续mov,将eax的值赋给[ebp-8],而[ebp-8]是我们上面创建好的z,一切都是那么的吻合!太美妙了!
我们在学习函数的时候,有一句话叫形参是实参的一份临时拷贝,现在看过来,这句话完全正确,因为我们在传参的时候,并没有独立去开辟新的空间去接收形参,而是通过寄存器去找到我们之前在主函数里压栈进去的实参!
10.
returnz; 002717A5moveax,dwordptr [ebp-8] } 002717A8popedi002717A9popesi002717AApopebx002717B8movesp,ebp002717BApopebp
mov 将[ebp-8]的值由eax保管
pop 意思是弹出,接下来就是函数栈帧的销毁,此时edi,esi,ebx就被销毁了
mov 将ebp的值赋给esp
pop 弹出ebp
到这里,红线以上的Add函数的栈帧就被销毁了
回过头看,我们为什么要将[ebp-8]的值先由eax保管,原因是[ebp-8] (也就是z的值)会销 毁,如果不由eax保管,那么返回值将带不出来!
10.
002717BBret
ret 返回值
此时栈顶上存放的就是call指令的下一条指令的地址,此时按F10,就直接跳到main函数的Add指令了
这就是我们为什么要存放call指令的下一条指令的地址,就是为了确保函数销毁时我还能回得来!这一套逻辑真的是太严密了!!!
11.
002718E7addesp,8002718EAmovdwordptr [ebp-20h],eax
add 将esp的地址+8,就回到了我们的edi上面了
此时红线以上的部分又被销毁了,此时的形参x,y的空间就释放了
mov 将eax的值赋给[ebp-20h],而此时的[ebp-20h]就是我们之前压栈的c的空间,eax使我们上面带回来的30,赋给了变量c,这一切又是那么的巧妙!!!
二 . 总结
当我们真正理通函数栈帧创建和销毁的过程,我们会产生一种敬畏之心(小编是有的),对前辈的敬畏,这么严密的底层逻辑思维,每一步汇编指令都是精心设计,回头来你会发现,原来当我们在写代码的时候,底层的一些东西原来是这样实现的,这个世界真的很奇妙!
如果对上文有意见或者有错误,还请大佬们斧正,觉得有帮助的童鞋们,蟹蟹三连!