您的位置:首页 > 其它

函数调用的汇编解释

2014-05-06 11:34 323 查看
最近看了下汇编,主要是想了解下cdecl和stdcall的区别。

之前没有汇编基础,只知道少许简单的汇编指令,如mov等等。这两天看了若干,总结一下吧,当然只是部分知识点,对我而言已经很受用了。

1. cdecl 和 stdcall 的区别(从汇编层面解释)

其实在选择这两者时,最主要的考虑是可变参数的问题,也就是谁负责清栈。那么从汇编层面是如何反应出来的呢?

1.1 stdcall 范式

int __stdcall add(int a, int b)
{
002613A0 push ebp      //保存调用者栈底地址
002613A1 mov ebp,esp   //设置新的被调用者的栈底指针
002613A3 sub esp,0C0h
002613A9 push ebx
002613AA push esi
002613AB push edi
002613AC lea edi,[ebp-0C0h]
002613B2 mov ecx,30h
002613B7 mov eax,0CCCCCCCCh
002613BC rep stos dword ptr es:[edi]
return a+b;
002613BE mov eax,dword ptr [a]
002613C1 add eax,dword ptr 
}
002613C4 pop edi
002613C5 pop esi
002613C6 pop ebx
002613C7 mov esp,ebp   //将栈顶指针设置为被调用函数帧栈栈底地址
002613C9 pop ebp       //还原调用者帧栈栈底地址
002613CA ret 8         //返回,ret 8 等价于 pop EIP; add sp 8。首先,恢复返回后执行指令的地址,然后,参数出栈,这里是两个int,故8个字节。
//这里就解释了stdcall是被调用者清栈

int main()
{
002613E0 push ebp
002613E1 mov ebp,esp
002613E3 sub esp,0CCh
002613E9 push ebx
002613EA push esi
002613EB push edi
002613EC lea edi,[ebp-0CCh]
002613F2 mov ecx,33h
002613F7 mov eax,0CCCCCCCCh
002613FC rep stos dword ptr es:[edi]
int sum;
sum = add(1,2);
002613FE push 2   //参数2压栈
00261400 push 1   //参数1压栈
00261402 call add (261109h)  //调用函数add
00261407 mov dword ptr [sum],eax
return 0;
0026140A xor eax,eax
}
0026140C pop edi
0026140D pop esi
0026140E pop ebx
0026140F add esp,0CCh
00261415 cmp ebp,esp
00261417 call @ILT+315(__RTC_CheckEsp) (261140h)
0026141C mov esp,ebp
0026141E pop ebp
0026141F ret


1.2 cdecl

int add(int a, int b)
{
00E713A0 push ebp
00E713A1 mov ebp,esp
00E713A3 sub esp,0C0h
00E713A9 push ebx
00E713AA push esi
00E713AB push edi
00E713AC lea edi,[ebp-0C0h]
00E713B2 mov ecx,30h
00E713B7 mov eax,0CCCCCCCCh
00E713BC rep stos dword ptr es:[edi]
return a+b;
00E713BE mov eax,dword ptr [a]
00E713C1 add eax,dword ptr [b]
}
00E713C4 pop edi
00E713C5 pop esi
00E713C6 pop ebx
00E713C7 mov esp,ebp
00E713C9 pop ebp
00E713CA ret       //对比上面那个,不同之处在于ret等价于pop IP。没有后续参数出栈的操作

int main()
{
00E713E0 push ebp
00E713E1 mov ebp,esp
00E713E3 sub esp,0CCh
00E713E9 push ebx
00E713EA push esi
00E713EB push edi
00E713EC lea edi,[ebp-0CCh]
00E713F2 mov ecx,33h
00E713F7 mov eax,0CCCCCCCCh
00E713FC rep stos dword ptr es:[edi]
int sum;
sum = add(1,2);
00E713FE push 2
00E71400 push 1
00E71402 call add (0E71096h)
00E71407 add esp,8              //看见没有,参数出栈的地方放到了调用者这里。
00E7140A mov dword ptr [sum],eax
return 0;
00E7140D xor eax,eax
}
00E7140F pop edi
00E71410 pop esi
00E71411 pop ebx
00E71412 add esp,0CCh
00E71418 cmp ebp,esp
00E7141A call @ILT+315(__RTC_CheckEsp) (0E71140h)
00E7141F mov esp,ebp
00E71421 pop ebp
00E71422 ret


通过上面的注释,应该很清楚了吧~

2.函数调用时汇编做的事儿

参见上面代码示例,可以总结下,函数调用时汇编是怎么做的

1)参数压栈,push &1, push&2...

2)调用call指令,这里call等价于push EIP,jmp XXX; 如果是lcall,那么等价于push CS, push EIP, jmp XXX,即保存函数返回后下一条指令地址,并转移到被调用函数指令

3)保存“调用者”栈基地址,push ebp

4)设置新的“被调用者”的栈基地址,mov ebp, esp

5)执行函数。。。

6)栈顶指针指向“被调用者”栈底 mov esp, ebp

7)还原“调用者”帧栈基址,pop ebp

8)还原函数返回后下一条指令地址,并根据调用方式决定是否清理参数帧栈,ret或ret n,也等价于pop EIP或 pop EIP, add esp, n。

很明确了吧?

附录:

intel 各个寄存器的用途

通用:

EAX(累加器)

EBX(基址)

ECX(计数)

EDX(数据)

EBP(基指针):为了传送存储器数据,EBP指向存储单元

EDI(目的地址):寻址指令的目的数据串

ESI(源变址):寻址指令的源数据串

专用:

EIP(指令指针):代码存储区的下一条指令

ESP(堆栈指针):堆栈

EFLAGS:指示处理器的状态并控制它的操作(详细了解)奇偶标准以1为标准

CS(代码段)实模式下:64KB;保护模式下:4GB;

DS(数据段)实模式下:64KB;保护模式下:4GB;

ES(附加段)附加的数据段,为某些串指令存放目的数据

SS(堆栈段)为堆栈定义了一个存储区域,由堆栈段和堆栈指针[b]寄存器
确定堆栈段内当前的入口地址,BP也可寻址堆栈段内的数据。

CS:EIP(CS:IP):代码起点:下一条指令的偏移地址

SS:ESP(SS:SP):寻址堆栈区:SS+ESP的存储单元,栈顶地址

SS:EBP(SS:BP):可以理解为子栈区的栈基址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: