在c语言函数调用时,主要有三种调用方式,下面进行分析:
下面的程序:
- #include
-
int main()
-
{
-
return 1;
-
}
-
(gdb) disassemble main
-
Dump of assembler code for function main:
-
0x080483b4 <+0>: push %ebp
-
0x080483b5 <+1>: mov %esp,%ebp
-
0x080483b7 <+3>: mov $0x1,%eax //返回值存放在eax寄存器中
-
0x080483bc <+8>: pop %ebp
-
0x080483bd <+9>: ret
为什么会有ebp?这里的ebp是为了标识该函数的基地址,本来esp可以实现该功能,但由于esp在分配和释放空间会经常发生改变,所以就采用了ebp来进行保存。
1.cdecl
编译器默认采用CDECL函数调用方式,该方式有以下特点:
1.行参采用从右向左传递,而且返回值通过寄存器eax返回
2.调用函数来清理被调用函数的栈空间(通过指令leave实现),这就允许CDECL的函数有可变长度参数列表的行参,这样参数的个数就不会添加到函数名的后面,汇编器和连接器就不能决定使用行参个数正确与否,所以可变参数都会通过va_start(),va_arg()的宏来访问可变参数,CDECL所有的函数前面都会有一个下划线标注。
如下面的程序:
- int add(int a,int b)
-
{
-
return a+b;
-
}
-
int add(int ,int) __attribute__((cdecl));
-
void print(int i, ...) {add(2,3);}
-
void print(int,...)__attribute__((cdecl));
-
void __attribute__((cdecl)) main(void)
-
{
-
print(2,3);
-
}
反汇编效果如下:
- (gdb) disassemble main
-
Dump of assembler code for function main:
-
0x080483de <+0>: push ebp
-
0x080483df <+1>: mov ebp,esp
-
0x080483e1 <+3>: sub esp,0x8
-
0x080483e4 <+6>: mov DWORD PTR [esp+0x4],0x3
-
0x080483ec <+14>: mov DWORD PTR [esp],0x2
-
0x080483f3 <+21>: call 0x80483c2
-
0x080483f8 <+26>: leave //释放了print的空间
-
0x080483f9 <+27>: ret
-
(gdb) disassemble print
-
Dump of assembler code for function print:
-
0x080483c2 <+0>: push ebp
-
0x080483c3 <+1>: mov ebp,esp
-
0x080483c5 <+3>: sub esp,0x8
-
0x080483c8 <+6>: mov DWORD PTR [esp+0x4],0x3
-
0x080483d0 <+14>:mov DWORD PTR [esp],0x2
-
0x080483d7 <+21>:call 0x80483b4
-
0x080483dc <+26>:leave //相当于执行指令:move esp,ebp pop ebp,修复栈的状态,释放了add的空间
-
0x080483dd <+27>:ret
-
(gdb) disassemble add
-
Dump of assembler code for function add:
-
0x080483b4 <+0>: push ebp
-
0x080483b5 <+1>: mov ebp,esp
-
0x080483b7 <+3>: mov eax,DWORD PTR [ebp+0xc]
-
0x080483ba <+6>: mov edx,DWORD PTR [ebp+0x8]
-
0x080483bd <+9>: lea eax,[edx+eax*1]
-
0x080483c0 <+12>:pop ebp
-
0x080483c1 <+13>:ret
2.stdcall
stdcall由微软制定并执行也就是通常的”WINAPI”形式,现在所有的编译器也包含该调用方式,主要来讲,该调用方式有以下特征:
1.stdcall也是从右到左传递参数,也是将参数返回至eax寄存器中。
2.与cdecl不同的是,被调用函数自己释放栈空间,这样针对可变参数就无能为力了,需要采用其它的方法。
测试程序如下:
- int __attribute__((stdcall)) add(int a,int b)
-
{
-
return a+b;
-
}
-
void __attribute__((stdcall)) print(int i,...) {add(2,3);}
-
void __attribute__((stdcall)) main(int argc ,char*argv[])
-
{
-
print(2,3);
-
}
反汇编程序如下:
- (gdb) disassemble main
-
Dump of assembler code for function main:
-
0x080483e3 <+0>: push ebp
-
0x080483e4 <+1>: mov ebp,esp
-
0x080483e6 <+3>: sub esp,0x8
-
0x080483e9 <+6>: mov DWORD PTR [esp+0x4],0x3
-
0x080483f1 <+14>: mov DWORD PTR [esp],0x2
-
0x080483f8 <+21>: call 0x80483c4
-
0x080483fd <+26>: leave
-
0x080483fe <+27>: ret 0x8 //释放8个字节的栈空间
-
(gdb) disassemble print
-
Dump of assembler code for function print:
-
0x080483c4 <+0>: push ebp
-
0x080483c5 <+1>: mov ebp,esp
-
0x080483c7 <+3>: sub esp,0x8
-
0x080483ca <+6>: mov DWORD PTR [esp+0x4],0x3
-
0x080483d2 <+14>:mov DWORD PTR [esp],0x2
-
0x080483d9 <+21>:call 0x80483b4
-
0x080483de <+26>:sub esp,0x8 //由于可变参数数量不确定因此不能进行自己释放只好由被调用函数释放
-
0x080483e1 <+29>:leave //自己来释放空间
-
0x080483e2 <+30>:ret
-
(gdb) disassemble add
-
Dump of assembler code for function add:
-
0x080483b4 <+0>: push ebp
-
0x080483b5 <+1>: mov ebp,esp
-
0x080483b7 <+3>: mov eax,DWORD PTR [ebp+0xc]
-
0x080483ba <+6>: mov edx,DWORD PTR [ebp+0x8]
-
0x080483bd <+9>: lea eax,[edx+eax*1]
-
0x080483c0 <+12>:pop ebp
-
0x080483c1 <+13>:ret 0x8 //释放8个字节
3.fastcall
fastcall调用规则不能完全在所有的编译器中通用,使用起来必须小心。在fastcall中,前2或3个
32位(或更小)的行参在寄存器中传递,而常用的寄存器有:edx,eax,ecx,而大于4字节的参数通过栈传递,当然也是从右到左的传递方式,如果必须的话,调用函数通常负责清除栈。该方式没有标准化,依赖具体的实现方式。
源代码如下:
- int __attribute__((fastcall)) add(int a,int b)
-
{
-
return a+b;
-
}
-
void __attribute__((fastcall)) print(int i, ...) {add(2,3);}
-
void __attribute__((fastcall)) main(void)
-
{
-
print(2,3);
-
}
反汇编如下:
- (gdb) disassemble main
-
Dump of assembler code for function main:
-
0x080483df <+0>: push ebp
-
0x080483e0 <+1>: mov ebp,esp
-
0x080483e2 <+3>: sub esp,0x8
-
0x080483e5 <+6>: mov DWORD PTR [esp+0x4],0x3
-
0x080483ed <+14>:mov DWORD PTR [esp],0x2
-
0x080483f4 <+21>: call 0x80483cb
-
0x080483f9 <+26>: leave
-
0x080483fa <+27>: ret
-
(gdb) disassemble print
-
Dump of assembler code for function print:
-
0x080483cb <+0>: push ebp
-
0x080483cc <+1>: mov ebp,esp
-
0x080483ce <+3>: mov edx,0x3
-
0x080483d3 <+8>: mov ecx,0x2
-
0x080483d8 <+13>:call 0x80483b4
-
0x080483dd <+18>:pop ebp //采用寄存器传参数,不用清除空间
-
0x080483de <+19>:ret
-
(gdb) disassemble add
-
Dump of assembler code for function add:
-
0x080483b4 <+0>: push ebp
-
0x080483b5 <+1>: mov ebp,esp
-
0x080483b7 <+3>: sub esp,0x8
-
0x080483ba <+6>: mov DWORD PTR [ebp-0x4],ecx
-
0x080483bd <+9>: mov DWORD PTR [ebp-0x8],edx
-
0x080483c0 <+12>:mov eax,DWORD PTR [ebp-0x8]
-
0x080483c3 <+15>:mov edx,DWORD PTR [ebp-0x4]
-
0x080483c6 <+18>:lea eax,[edx+eax*1] //将结果存放至寄存器eax中
-
0x080483c9 <+21>:leave //清除自己栈的空间
-
0x080483ca <+22>: ret
阅读(1841) | 评论(0) | 转发(0) |