windows下函数参数入栈顺序和栈帧结构

栈帧布局

栈帧在函数调用时进行构建,以进行内存的隐式分配。内存可以显示的通过malloc(), calloc(), realloc(), new, free和delte在堆上进行申请和释放。不同的操作系统的栈帧布局可能不同,一个典型的栈帧布局如下所示:
  1. 函数参数
  2. 函数返回地址
  3. 帧指针
  4. 错误处理帧
  5. 局部变量
  6. 栈缓冲区
  7. 被调函数保存的寄存器

栈帧的布局如下图所示:


从布局图中很容易可以看出如果发生栈缓冲区溢出,将会复写比缓冲区地址高的那些变量的值,包括局部变量, 异常处理帧, 栈帧指针, 返回地址和函数参数。下面我们仔细分析一下。

在windows/Intel平台上,当发生一个函数调用时,数据通过如下的方式存放在栈上:

  1. 在函数调用之前先将函数参数压栈,参数按照从右到左的顺序。
  2. 由x86的call指令将函数的返回地址压栈,这个返回地址就是EIP寄存器的值。
  3. 上一个栈帧的栈帧指针通过EBP的值压栈
  4. 如果函数包含了try/catch或者其他的异常处理结构(如SEH),编译器会在栈上放置异常处理所需要的信息。
  5. 栈上分配局部变量
  6. 为临时数据分配栈缓冲区
  7. 最后,被调函数保存ESI, EDI, EBX寄存器的值。 对于linux/intel平台,这一步在第4步之后

对于一个windows函数 

void Func(int a, char *ch);
int i = 0x12345678;
char *str = "abcd";
Func(i, str);

//a ,b,c的ASCII码分别为 61 62 63

那么函数执行到Func(i, str);的时候,参数在栈中的形式是怎么样的?


首先:我们知道 函数调用的时候,一般情况下,参数的压栈顺序从右到左,然后就是函数地址。 也就是 str-i-&Func 的顺序。

然后,我们还要知道在Windows平台上,栈的生长方向是从高地址向低地址生长的,也就是先入栈的元素是高地址。

所以要在以下2个选项来选择正确结果的话(左边为地址,右边为数据),应该是A:

A: 0x0012FF00 0x0012FF08//函数地址

0x0012FF040x12345678//参数i

0x0012FF080x61626364//参数str


B:0x0012FF08 0x61626364//参数str

0x0012FF040x12345678//参数i

0x0012FF000x0012FF08//函数地址


注意压站顺序和栈的生长方向


测试不同系统中栈的生长方向的一段代码:

#include <stdio.h>
#include <stdlib.h>
void func1();
void func2(int *a);
int main(int argc, char** argv) {
    func1();
    return (EXIT_SUCCESS);
}
void func1(){
    int a = 0;
    func2(&a);
}
void func2(int *a){
    int b = 0;
    printf("%x\n%x\n",a,&b);
}

这里涉及两个函数调用,第一个函数func1中申明了一个变量,然后将指向其的指针传递给函数func2,然后再在func2中申明一个变量,打印出两个变量的地址,比较地址的大小,就可知道栈的增长方向了,如果func1里的变量地址比func2里的变量地址大,则说明栈的增长方向是由高地址向低地址增长的,反之,则说明栈的增长方向是由低地址向高地址增长的。






  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值