Chinaunix首页 | 论坛 | 博客
  • 博客访问: 314226
  • 博文数量: 60
  • 博客积分: 2781
  • 博客等级: 少校
  • 技术积分: 600
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-23 16:42
文章分类

全部博文(60)

文章存档

2011年(33)

2010年(27)

分类: C/C++

2011-01-07 23:02:48

函数调用过程

栈用来存放函数调用过程中使用的数据,栈和函数调用之间的关系非常紧密,是发生函数调用(对于任何编译器)必须要使用的一种内存形式。对于栈的操作,非常重要的指针就是EBPESPEBPstack frame pointer,指向栈基底;ESPstack pointer,指向栈顶。

步骤详解:

1.        调用函数(calling function)即主调函数在函数调用发生前先将被调函数的参数压栈,即一系列的push指令。

2.        call指令将函数调用完成后的返回地址(当前EIP寄存器的值)压栈,计算被调函数的地址将其放入EIP中,跳转执行。

3.        当前栈帧指针压栈,然后重新设置栈帧指针和堆栈指针,多条指令完成这个工作:a:push ebp b:mov ebp esp c:sub esp, OPER

4.        函数执行过程中,EBP作为内存空间的参考指针。函数的局部变量存放在堆栈上,当函数调用结束后,需要完成堆栈平衡,即清理工作。

5.        函数调用结束时,调用leave或者mov esp, ebp & pop ebp,完成堆栈的平衡操作。这里提到的leavemov esp, ebp & pop ebp,两者作用相同,不同编译器采用了不同的实现方式,不过leave的效率要高于后者。

6.        堆栈平衡完成后,调用retn OPER指令,其中OPER表示从栈中弹出的字节数目。对于retn它只是完成一个操作,即EIP = [ESP]获得了函数调用完成后的返回地址。这里retn需要根据函数调用约定(下面已讲解)来设置OPER的值,如果调用函数负责清理剩下的内容(即StdCall),那么retn OPER;如果主调函数负责清理(即Cdecl),那么直接retn即可,然后主调函数会在call指令之后添加ADD ESPoper指令。

7.        控制权交给主调函数,主调函数通过查询EAX寄存器获取被调函数的返回值。

 

总结步骤可得procedure prologue:栈帧中从上至下分别是:函数调用的参数(params)、函数的返回地址(return address)、保存的主调函数的栈基址(EBP-old)然后下面就是被调函数的局部变量。

当函数调用结束后,需进行堆栈平衡操作,涉及编译器的调用约定。

函数调用约定(Calling Convention)

三种常用的调用约定:PascalCdeclStdCall。所谓调用约定就是规定函数参数的传递顺序以及堆栈平衡的方式。堆栈平衡方式指的是函数调用完成后由谁负责清除堆栈,俗称堆栈平衡。

使用Delphi编写的程序遵从Pascal调用约定;C/C++/Java编写的程序都遵从Cdecl调用约定;WindowsAPI调用遵从的是StdCall调用约定。

假设有一高级语言函数Function(param1, param2, param3),按照不同的调用约定,其参数传递顺序和堆栈平衡方式见下表:

Pascal调用约定

Cdecl调用约定

StdCall调用约定

push param1

push param2

push param3

call Function

push param3

push param2

push param1

call Function

add ESP, 0CH

push param3

push param2

push param1

call Function

参数从左向右传递

被调函数清理堆栈

参数从右向左传递

调用函数清理堆栈

参数从右向左传递

被调函数清理堆栈

其它的调用约定如fastcall,就是将参数送入寄存器而不是压入内存栈中,减少访问内存的次数,加快速度。

阅读(1612) | 评论(0) | 转发(0) |
0

上一篇:Fedora14安装总结

下一篇:const 位置解释

给主人留下些什么吧!~~