函数调用的汇编解释

最近看了下汇编,主要是想了解下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 [b]

}

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(堆栈段)为堆栈定义了一个存储区域,由堆栈段和堆栈指针寄存器确定堆栈段内当前的入口地址,BP也可寻址堆栈段内的数据。

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

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

SS:EBP(SS:BP):可以理解为子栈区的栈基址

 

你可能感兴趣的:(函数)