avatar

函数调用约定以及栈

栈帧

栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。为单个过程(函数调用)分配的那部分栈称为栈帧。栈帧其实 是两个指针寄存器,寄存器ebp为帧指针(指向该栈帧的最底部),而寄存器esp为栈指针(指向该栈帧的最顶部),当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的,换句话说,就是如果该栈存在,ebp帧指针是不移动的,访问栈里面的元素可以以ebp为基准访问ebp指针下面或者上面的元素)。

概括起来就是,栈帧的主要作用是用来控制和保存一个过程的所有信息的。栈帧结构如下所示:

upload successful
栈是从高地址向低地址存储。所以越是低的地址,越是靠后入栈。

upload successful
栈帧对应的汇编代码如下:
upload successful
下面对一个简单的加法函数调用的程序进行反编译来分析函数栈帧,程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int Add(int x, int y)
{
int a = x;
int b = y;
return a+b;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b) ;
printf("%d\n", ret) ;
return 0;
}

反编译的代码如下:
main()函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
push ebp //将ebp压入栈
mov ebp,esp //帧指针从前面栈帧移动到调用函数的栈帧底部
sub esp,0x4C //开辟0x4c大的空间来存储变量
push ebx
push esi
push edi
lea edi,[local.19]
mov ecx,0x13
moc eax,0xCCCCCCCC
rep stos dword ptr es:[edi] //上面的三句话是用0xCCCCCCCC初始化刚开辟的空间
mov [local.1],0xA //给local.1赋值10
mov [local.2],0x14//给local赋值20
mov eax,[local.2]
push eax
mov ecx,[local.1]
push ecx //变量逆序压入栈
call stack.00401005 //调用add()函数
add esp,0x8 //栈指针移动,add()函数调用的参数失效
mov [local.3],eax //eax中存着add()函数运算的结果,赋值给变量
mov edx,[local.3]
push edx //edx存着add()函数的结果,压入栈
push stack.0042201C //ASCII %d\n
call stack.printfgvdbgind_blockeressges //调用printf函数输出结果
add esp,0x8 //栈指针移动,printf调用的参数失效
xor eax,eax //栈指针移动
pop edi
pop esi
pop ebx
add esp,0x4C
cmp ebp,esp
call stack._chkespleBufferstringswteApa
mov esp,ebp
pop ebp //栈帧指针恢复到起始状态
retn //程序结束

add()函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
push ebp //现将ebp压入栈
mov ebp,esp //帧指针从调用函数移动到被调用函数的栈帧底部
sub esp,0x48 //开辟0x48大的空间用来存储变量
push ebx
push esi
push edi
lea edi,[local.18]
mov ecx,0x12
mov eax,0xCCCCCCCC
rep stos dword ptr es:[edi] //0xCCCCCCCC问题开辟
mov eax,[arg.1]
mov [loval.1],eax
mov ecx,[arg.2]
mov [local.2],ecx //传参
mov eax,[local.1]
add eax,[local.2] //加法,传入的参数相加
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
retn //恢复栈指针,函数结束

栈帧指针绘图分析:
upload successful

开始执行main()函数的状态:

upload successful

执行了mov ebp,esp之后栈帧指针的变化

upload successful

可见在19FF40处保留着19FF80,它是初始EBP的值

main()中的赋值语句如下:

1
2
mov [local.1],0xA
mov [local.2].0x14

执行完赋值语句后ESP的值如下;
upload successful

ESP的值增加了,而EBP始终作为当前函数操作的基准
add()函数调用参数如下:

1
2
3
4
mov eax,[local.2]
push eax
mov ecx,[local.1]
push ecx

因为栈的特性,所以是逆序压入栈,参数入栈后再call add()函数,再看一下堆栈的情况:
upload successful

确实是逆序的,(不过不知道为什么要弄这么多空间)
执行完add()的mov ebp,esp之后,栈帧变化如下:
upload successful

1号箭头指向的是刚压入栈的参数,2号箭头指向的是原EBP的值,因为EBP现在指向被调用函数的栈帧底部,所以基准发生变化,可以参考上一张图片比较
add函数内加法运算,先把传入参数的值赋给eax和ecx,在把eax和ecx的值传给函数内的临时变量local.1和local.2,再相加
upload successful
add()函数运行完后执行恢复栈帧的语句,执行完后栈帧变化如下:
upload successful
首先EBP的值变回了原来的值,而且EBP指向最开始的EBP的值,所以在调用函数结束后栈帧会恢复到原来的状态
upload successful
执行完add()函数后,esp加8,所以add()函数内的局部变量不可用了,因为栈帧无法到达参数所在的地址
接着就是把结果压入栈,然后调用printf()函数,这个过程跟调用add()函数大同小异
调用完printf()函数后,esp加8,所以函数内的局部变量不可用了,接着esp又加0x4c,栈帧移动,此时整个main()函数内的变量都失效了,所以一开始移动这么多是考虑整个main()可能需要声明使用很多变量
upload successful
最后main()结束,栈帧恢复到最开始的状态
upload successful
不过这里有个细节,刚才提到了ESP加8,相当于删除被调用函数内的局部变量,但其实函数执行完后,是不用管栈中的参数的,由于只是临时使用存储在栈中的值,下一次在存入其他的值自然会覆盖掉原有值,而且栈内存是固定的,所以既不能也没有必要释放内存。
除此之外,还有个细节,ESP加8是在main()中执行的,其实也是可以在被调用函数中执行,这就是另一个知识点了,函数调用约定里面会对这些加以规定。
函数调用约定(calling convention):对函数调用时如何传递参数的一种约定。
通过前面对函数栈帧的分析,我们知道调用函数前要先把参数压入栈传递给函数。栈内存是固定的,ESP用来指示栈的当前位置,函数调用约定就是解决函数调用后如何处理ESP。主要的函数调用约定有:
cdecl
stdcall
fastcall

一.cdecl

1、采用桟传递参数,参数从右向左依次入栈;

2、由调用者负责恢复栈顶指针;

3、在函数名前加上一个下划线前缀,格式为
_function;
要注意的是,调用参数个数可变的函数只能采用这种方式(如printf)。
上面演示的程序就属于cdcel

二.stdcall

1)采用桟传递全部参数,参数从右向左压入栈;

2)被调用函数负责恢复栈顶指针 ;

3)函数名自动加前导的下划线,后面是函数名,之后紧跟一个@符号,其后紧跟着参数的尺寸,例如_function@4;

因此,stdcall与cdecl的区别就是谁负责恢复栈顶指针和编译后函数的名称问题。

三.fastcall
采用fasecall的函数声明方式为:
int __fastcall function(int a,int b)
fastcall调用约定意味着:
1、函数的第一个和第二个(从左向右)32字节参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过桟传递。从第三个参数(如果有的话)开始从右向左的顺序压栈;
2、被调用函数恢复栈顶指针;
3、在函数名之前加上"@",在函数名后面也加上“@”和参数字节数,例如@function@8;

upload successful
这个图很重要

Author: L0x1c
Link: https://l0x1c.github.io/2019/11/18/2019-11-18/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶