汇编之函数调用(CALL)分析
问题来源:
stdcall类型函数调用,参数倒序入栈,第一个参数最后入栈,函数内部访问第一个参数时经常看到用[ebp + $08]表示,ebp通常来自esp,即函数首部经常看到的push ebp, mov ebp, esp,为什么栈顶偏移$08才是第一个参数呢?按道理说第一个参数最后入栈,栈顶不应该就是第一个参数么?事实并非如此...
问题探索与解答:
函数1:
1 function add:Integer;stdcall;
2 begin
3 Result :=10;
4 end;
调用该函数,观察汇编代码:(注意EIP寄存器变化)
0044D944 E8F3FFFFFF call add ;此时EIP=0044D944
0044D949 C3 ret
跟进函数CALL
Unit1.pas.27: Result :=10;
0044D93C B80A000000 mov eax,$0000000a
0044D941 C3 ret
此函数无参,跟进发现栈顶为 0044D949,这正是执行完CALL之后的下一句要执行的代码
函数2:
1 function add(a: Integer):Integer;stdcall;
2 begin
3 Result := a + 10;
4 end;
调用该函数,观察汇编代码:(注意EIP寄存器变化及栈内栈顶到第一参数间的数值)
Unit1.pas.45: s := add(12);
0044D94C 6A0C push $0c
0044D94E E8E9FFFFFF call add ;此时EIP=0044D94E,跟进
0044D953 C3 ret
跟进CALL
Unit1.pas.31: begin
0044D93C 55 push ebp
0044D93D 8BEC mov ebp,esp
Unit1.pas.32: Result := a + 10;
0044D93F 8B4508 mov eax,[ebp+$08];执行到此行,观察
0044D942 83C00A add eax,$0a
Unit1.pas.33: end;
0044D945 5D pop ebp
0044D946 C20400 ret $0004
栈顶是push ebp保存进EBP当时的值,结束前恢复现场用
[ebp+$08]是访问的第一个参数
那[ebp+$04]是何作用呢,又是何时进到栈里的呢?
观察发现[ebp+$04]= 0044D953,是CALL返回后要执行的下一句
问题解答:
因此,在调用函数的时候,程序会偷偷的将执行CALL之后的下一条语句的地址压入栈中,同时呢,一般带有参数的程序,进入程序内部后会首先保存当时的EBP,这就造成了栈内多出两个数值,而这两个又都是在参数入栈之后发生的,因此第一个参数的地址就变成了[EBP+$08]。
明眼人也发现了,函数1返回时用的是ret直接返回,而函数2返回时是ret $0004, 这又是何意呢?原来ret时就自动把CALL之后的下一条语句的地址出栈了,后面的$0004意味着出栈后ESP这个栈顶指针要进行的偏移,因为你传进的这些参数在函数返回后它们就没必要存在栈里面了,所以移动下栈顶指针就可以。
ret $0004= ESP + $0004