Chinaunix首页 | 论坛 | 博客
  • 博客访问: 493478
  • 博文数量: 72
  • 博客积分: 1851
  • 博客等级: 上尉
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-16 17:50
文章分类

全部博文(72)

文章存档

2013年(1)

2012年(17)

2011年(51)

2010年(3)

分类: C/C++

2011-10-11 09:29:42

c语言函数调用时,主要有三种调用方式,下面进行分析:

下面的程序:

  1. #include
  2. int main()
  3. {
  4. return 1;
  5. }
  6. (gdb) disassemble main
  7. Dump of assembler code for function main:
  8. 0x080483b4 <+0>: push %ebp
  9. 0x080483b5 <+1>: mov %esp,%ebp
  10. 0x080483b7 <+3>: mov $0x1,%eax //返回值存放在eax寄存器中
  11. 0x080483bc <+8>: pop %ebp
  12. 0x080483bd <+9>: ret

为什么会有ebp?这里的ebp是为了标识该函数的基地址,本来esp可以实现该功能,但由于esp在分配和释放空间会经常发生改变,所以就采用了ebp来进行保存。

1.cdecl

编译器默认采用CDECL函数调用方式,该方式有以下特点:

1.行参采用从右向左传递,而且返回值通过寄存器eax返回

2.调用函数来清理被调用函数的栈空间(通过指令leave实现),这就允许CDECL的函数有可变长度参数列表的行参,这样参数的个数就不会添加到函数名的后面,汇编器和连接器就不能决定使用行参个数正确与否,所以可变参数都会通过va_start(),va_arg()的宏来访问可变参数,CDECL所有的函数前面都会有一个下划线标注。

如下面的程序:

  1. int add(int a,int b)
  2. {
  3.         return a+b;
  4. }
  5. int add(int ,int) __attribute__((cdecl));
  6. void print(int i, ...) {add(2,3);}
  7. void print(int,...)__attribute__((cdecl));
  8. void __attribute__((cdecl)) main(void)
  9. {
  10.         print(2,3);
  11. }

反汇编效果如下:

  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. 0x080483de <+0>: push ebp
  4. 0x080483df <+1>: mov ebp,esp
  5. 0x080483e1 <+3>: sub esp,0x8
  6. 0x080483e4 <+6>: mov DWORD PTR [esp+0x4],0x3
  7. 0x080483ec <+14>: mov DWORD PTR [esp],0x2
  8. 0x080483f3 <+21>: call 0x80483c2
  9. 0x080483f8 <+26>: leave //释放了print的空间
  10. 0x080483f9 <+27>: ret
  11. (gdb) disassemble print
  12. Dump of assembler code for function print:
  13. 0x080483c2 <+0>: push ebp
  14. 0x080483c3 <+1>: mov ebp,esp
  15. 0x080483c5 <+3>: sub esp,0x8
  16. 0x080483c8 <+6>: mov DWORD PTR [esp+0x4],0x3
  17. 0x080483d0 <+14>:mov DWORD PTR [esp],0x2
  18. 0x080483d7 <+21>:call 0x80483b4
  19. 0x080483dc <+26>:leave //相当于执行指令:move esp,ebp pop ebp,修复栈的状态,释放了add的空间
  20. 0x080483dd <+27>:ret
  21. (gdb) disassemble add
  22. Dump of assembler code for function add:
  23. 0x080483b4 <+0>: push ebp
  24. 0x080483b5 <+1>: mov ebp,esp
  25. 0x080483b7 <+3>: mov eax,DWORD PTR [ebp+0xc]
  26. 0x080483ba <+6>: mov edx,DWORD PTR [ebp+0x8]
  27. 0x080483bd <+9>: lea eax,[edx+eax*1]
  28. 0x080483c0 <+12>:pop ebp
  29. 0x080483c1 <+13>:ret

2.stdcall

stdcall由微软制定并执行也就是通常的”WINAPI”形式,现在所有的编译器也包含该调用方式,主要来讲,该调用方式有以下特征:

1.stdcall也是从右到左传递参数,也是将参数返回至eax寄存器中。

2.cdecl不同的是,被调用函数自己释放栈空间,这样针对可变参数就无能为力了,需要采用其它的方法。

测试程序如下:

  1. int __attribute__((stdcall)) add(int a,int b)
  2. {
  3.         return a+b;
  4. }
  5. void __attribute__((stdcall)) print(int i,...) {add(2,3);}
  6. void __attribute__((stdcall)) main(int argc ,char*argv[])
  7. {
  8.         print(2,3);
  9. }

反汇编程序如下:

  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. 0x080483e3 <+0>: push ebp
  4. 0x080483e4 <+1>: mov ebp,esp
  5. 0x080483e6 <+3>: sub esp,0x8
  6. 0x080483e9 <+6>: mov DWORD PTR [esp+0x4],0x3
  7. 0x080483f1 <+14>: mov DWORD PTR [esp],0x2
  8. 0x080483f8 <+21>: call 0x80483c4
  9. 0x080483fd <+26>: leave
  10. 0x080483fe <+27>: ret 0x8 //释放8个字节的栈空间
  11. (gdb) disassemble print
  12. Dump of assembler code for function print:
  13. 0x080483c4 <+0>: push ebp
  14. 0x080483c5 <+1>: mov ebp,esp
  15. 0x080483c7 <+3>: sub esp,0x8
  16. 0x080483ca <+6>: mov DWORD PTR [esp+0x4],0x3
  17. 0x080483d2 <+14>:mov DWORD PTR [esp],0x2
  18. 0x080483d9 <+21>:call 0x80483b4
  19. 0x080483de <+26>:sub esp,0x8 //由于可变参数数量不确定因此不能进行自己释放只好由被调用函数释放
  20. 0x080483e1 <+29>:leave //自己来释放空间
  21. 0x080483e2 <+30>:ret
  22. (gdb) disassemble add
  23. Dump of assembler code for function add:
  24. 0x080483b4 <+0>: push ebp
  25. 0x080483b5 <+1>: mov ebp,esp
  26. 0x080483b7 <+3>: mov eax,DWORD PTR [ebp+0xc]
  27. 0x080483ba <+6>: mov edx,DWORD PTR [ebp+0x8]
  28. 0x080483bd <+9>: lea eax,[edx+eax*1]
  29. 0x080483c0 <+12>:pop ebp
  30. 0x080483c1 <+13>:ret 0x8 //释放8个字节

3.fastcall

fastcall调用规则不能完全在所有的编译器中通用,使用起来必须小心。在fastcall中,前2332(或更小)的行参在寄存器中传递,而常用的寄存器有:edx,eax,ecx,而大于4字节的参数通过栈传递,当然也是从右到左的传递方式,如果必须的话,调用函数通常负责清除栈。该方式没有标准化,依赖具体的实现方式。

源代码如下:

  1. int __attribute__((fastcall)) add(int a,int b)
  2. {
  3.         return a+b;
  4. }
  5. void __attribute__((fastcall)) print(int i, ...) {add(2,3);}
  6. void __attribute__((fastcall)) main(void)
  7. {
  8.         print(2,3);
  9. }

反汇编如下:

  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. 0x080483df <+0>: push ebp
  4. 0x080483e0 <+1>: mov ebp,esp
  5. 0x080483e2 <+3>: sub esp,0x8
  6. 0x080483e5 <+6>: mov DWORD PTR [esp+0x4],0x3
  7. 0x080483ed <+14>:mov DWORD PTR [esp],0x2
  8. 0x080483f4 <+21>: call 0x80483cb
  9. 0x080483f9 <+26>: leave
  10. 0x080483fa <+27>: ret
  11. (gdb) disassemble print
  12. Dump of assembler code for function print:
  13. 0x080483cb <+0>: push ebp
  14. 0x080483cc <+1>: mov ebp,esp
  15. 0x080483ce <+3>: mov edx,0x3
  16. 0x080483d3 <+8>: mov ecx,0x2
  17. 0x080483d8 <+13>:call 0x80483b4
  18. 0x080483dd <+18>:pop ebp //采用寄存器传参数,不用清除空间
  19. 0x080483de <+19>:ret
  20. (gdb) disassemble add
  21. Dump of assembler code for function add:
  22. 0x080483b4 <+0>: push ebp
  23. 0x080483b5 <+1>: mov ebp,esp
  24. 0x080483b7 <+3>: sub esp,0x8
  25. 0x080483ba <+6>: mov DWORD PTR [ebp-0x4],ecx
  26. 0x080483bd <+9>: mov DWORD PTR [ebp-0x8],edx
  27. 0x080483c0 <+12>:mov eax,DWORD PTR [ebp-0x8]
  28. 0x080483c3 <+15>:mov edx,DWORD PTR [ebp-0x4]
  29. 0x080483c6 <+18>:lea eax,[edx+eax*1] //将结果存放至寄存器eax中
  30. 0x080483c9 <+21>:leave //清除自己栈的空间
  31. 0x080483ca <+22>: ret


阅读(1841) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~