首页
社区
课程
招聘
[原创]函数的栈帧结构
发表于: 2024-8-7 09:39 1961

[原创]函数的栈帧结构

2024-8-7 09:39
1961

函数调用是一个压栈/出栈的过程,在此过程中每个函数在栈中都有自己的一片内存区域,称之为”栈帧“。
在VC++6.0上,可以通过调试查看栈帧的运行过程。

之所以用6.0老版本,是因为它的内存地址是固定的,便于观察。

准备一段C代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int func(int x, int y, int z, int m)
{
    return x + y + z + m;
}
  
int main(int argc, char * argv[])
{
    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += i;
    }
 
    func(4, 5, 6, sum);
    return 0;
}

按F10调试暂停至main函数,打开内存窗口跳转至esp处,也就是栈指针寄存器当前指向的地方:

从esp起,第一个双字是main函数执行完毕后的返回地址(可以记一下,待会儿验证),第二个双字是参数argc,第三个参数是argv。
可以跳转到argv保存的地址处(0x00380fd0,小端序)查看一下保存了什么:

这里保存了一个地址,地址指向的就是下一个四字,这里保存的是argv[0],也就是当前可执行文件全路径。
所以,在进入main函数时,栈内存布局如下:

接下来打开反汇编窗口

前6个指令,将ebp压栈,再将当前esp的值传送给ebp,于是现在ebp作为main函数的栈底;接下来将esp减0x48,相当于在栈中开辟了72字节内存,这72字节将会用来保存main的局部变量;接下来依次压入ebx,esi,edi。
于是,现在的栈帧变成这样:

接下来的指令:

让edi指向刚刚开辟的0x48个字节,向里面填充0xcccccccc,于是这四条指令执行完毕后,这片内存区域是这样子:

在GBK编码中,0xCCCC是汉字“烫”,初学C语言时,经常在黑窗口看到“烫烫烫……”便是这个原因了。

接下来给局部变量sum和i赋值:

这里可以看到sum和i都是通过栈底ebp来定位的,这就是之前要把esp的值传送给ebp的原因,栈底是固定的,方便定位,而栈顶esp是动态变化的。
局部变量初始化完成后,这两个内存位置已置0:

接下来运行至调用func处:

先将内存地址ebp-4(变量sum)处的值传送给edx,再依次压入edx,6,5,4四个参数,然后执行call指令调用func函数,此时会将func的返回地址,也就是call指令的下一条指令地址0x004010b1压入栈:

此时的栈帧结构:

接下来是func函数:

还是熟悉的配方,再次压入ebp,将当前esp传送给ebp作为栈底,在栈中开辟0x40字节空间并初始化为0xcccccccc,于是func的栈帧结构变为:

如图,每个函数在栈中都如同胶片一样一帧一帧,故名为“栈帧”。

执行完func的代码,准备返回:

依次弹出edi,esi,ebx,再将ebp的值传送给esp,再弹出前一个ebp的值,于是栈帧结构恢复为这样:

此时esp指向了返回地址,然后执行ret指令,代码跳转回0x004010b1处执行:

因为func是C约定__cdecl,这里需要由调用者清理参数空间,所以将esp加0x10字节,然后栈帧结构变为这样:

接下来是main函数的返回过程:

先用xor指令将eax清0作为返回值,后面还是熟悉的味道,依次弹出edi,esi,ebx,再将esp加0x48字节,恢复上一个ebp,执行ret指令跳转回0x00401209处执行:

后面就是C运行时库CRT0.c中的内容了。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-8-9 15:09 被米龙·0xFFFE编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//