函数参数压栈,栈帧ebp,esp怎样移动的?

压栈一次esp-4,ebp不变

esp是栈顶指针寄存器,堆栈操作只和esp有关
比如有一个函数a,有两个参数,一般是这样的
PUSH 1 参数2压栈,esp-4
PUSH 2 参数1压栈,esp-4
CALL a 调用

a:
PUSH EBP 保存ebp
MOV EBP,ESP 改变栈帧,以后访问参数通过ebp,访问局部变量通过esp
SUB ESP,8 分配局部变量空间

...
ADD ESP,8
POP EBP 恢复ebp
RETN 8 返回,esp+8

C语句对应汇编语句:

例如函数:

int aaa(int a,int b)
{
int c;
c=a+b;
return c;
}

aaa(1,2);

调试版aaa的代码
PUSH EBP
MOV EBP,ESP
SUB ESP,4//分配局部变量空间,一个int是4个字节
MOV EAX,DWORD PTR SS:[EBP+8]//读取参数a
ADD EAX,DWORD PTR SS:[EBP+C]//加上参数b
MOV DWORD PTR SS:[EBP-4],EAX//保存到局部变量c
MOV EAX,DWORD PTR SS:[EBP-4]//eax是返回值
MOV ESP,EBP//恢复栈顶指针
POP EBP//恢复ebp
RETN//返回

调用
PUSH 2//参数2压栈,esp-4
PUSH 1//参数1压栈,esp-4
CALL aaa//调用函数
ADD ESP,8//esp+8,平衡堆栈,清除掉参数

发布版的就是这样了,精简掉了很多内容
MOV EAX,DWORD PTR SS:[ESP+8]
MOV ECX,DWORD PTR SS:[ESP+4]
ADD EAX,ECX
RETN

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

下面要讲的是子程序如何存取参数,因为缺省对堆栈操作的寄存器有 ESP 和 EBP,而 ESP是堆栈指针,无法暂借使用,所以一般使用 EBP 来存取堆栈,假定在一个调用中有两个参数,而且在 push 第一个参数前的堆栈指针 ESP 为 X,那么压入两个参数后的 ESP 为 X-8,程序开始执行 call 指令,call 指令把返回地址压入堆栈,这时候 ESP 为 X-C,这时已经在子程序中了,我们可以开始使用 EBP 来存取参数了,但为了在返回时恢复 EBP 的值,我们还是再需要一句 push ebp 来先保存 EBP 的值,这时 ESP 为 X-10,再执行一句 mov ebp,esp,根据上图可以看出,实际上这时候 [ebp + 8] 就是参数1,[ebp + c]就是参数2。另外,局部变量也是定义在堆栈中的,它们的位置一般放在 push ebp 保存的 EBP 数值的后面,局部变量1、2对应的地址分别是 [ebp-4]、[ebp-8],下面是一个典型的子程序,可以完成第一个参数减去第二个参数,它的定义是:

MyProc proto Var1,Var2 ;有两个参数
   local lVar1,lVar2 ;有两个局部变量

注意,这里的两个 local 变量实际上没有被用到,只是为了演示用,具体实现的代码是:

MyProc proc

push ebp
   mov ebp,esp
   sub esp,8
   mov eax,dword ptr [ebp + 8]
   sub eax,dword ptr [ebp + c]
   add esp,8
   pop ebp
   ret 8

MyProc endp

现在对这个子程序分析一下,push ebp/mov ebp,esp 是例行的保存和设置 EBP 的代码,sub esp,8 在堆栈中留出两个局部变量的空间,mov /add 语句完成相加,add esp,8 修正两个局部变量使用的堆栈,ret 8 修正两个参数使用的堆栈,相当于 ret / add esp,8 两句代码的效果。可以看出,这是一个标准的 Stdcall 约定的子程序,使用时最后一个参数先入堆栈,返回时由子程序进行堆栈修正。当然,这个子程序为了演示执行过程,使用了手工保存 ebp 并设置局部变量的方法,实际上,386 处理器有两条专用的指令是完成这个功能用的,那就是 Enter 和 Leave,Enter 语句的作用就是 push ebp/mov ebp,esp/sub esp,xxx,这个 xxx 就是 Enter 的,Leave 则完成 add esp,xxx/pop ebp 的功能,所以上面的程序可以改成:

MyPorc proc

enter 8,0

mov eax,dword ptr [ebp + 8]
   sub eax,dword ptr [ebp + c]

leave
   ret 8

MyProc endp

文章出处:飞诺网(www.diybl.com):http://www.diybl.com/course/3_program/hb/hbjs/20071226/93663_2.html

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

一:在分析汇编代码时总是要遇到无数的 Call ,对于这些 Call ,尽量要根据 Call 之前传递的参数和 Call 的返回值来判断 Call 的功能。传递参数的工作必须由函数调用者和函数本身来协调,计算机提供了一种被称为栈的数据结构来支持参数传递。 
    当参数个数多于一个时,按照什么顺序把参数压入堆栈。函数调用后,由谁来把堆栈恢复。在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

二:堆栈框架也称为活动记录,它为程序的返回地址,传递进来的参数,保存的寄存器喝局部变量保存的堆栈空间。堆栈框架是按以下的步骤创建的。

1 :参数被压入堆栈。

2 :过程被调用,返回地址被压入堆栈。

3 :过程开始执行时, EBP 被压入堆栈。

4 :使 EBP 和 ESP 的值相等,从这里开始, EBP 就作为寻址参数的基址指针。

5 :可以从 ESP 中减掉一个数值来给过程的局部变量创建空间。

【例】按 __stdcall 约定调用函数 test2(Par1, Par2)

push par2  ;  参数 2              ; 参数被压入堆栈(从右向左) 
        push par1  ;  参数 1              ;
        call test2;                            ; 过程被调用 
        {                                ; 过程开始执行 
             push ebp                    ; EBP 被压入堆栈,保护现场原先的 EBP 指针 
             mov  ebp, esp            ; 使 EBP=ESP,  设置新的 EBP 指针,指向栈顶,

; 从这里开始, EBP 就作为寻址参数的基址指针。 
             mov  eax, [ebp+0C]  ;  调用参数 2
             mov  ebx, [ebp+08]   ;  调用参数 1
             sub  esp, 8              ;  若函数要用局部变量,则要在堆栈中留出点空间

; 从 ESP 中减掉一个数值来给过程的局部变量创建空间 
             …
             add  esp, 8                 ;  释放局部变量占用的堆栈 
             pop  ebp                    ;  恢复现场的 ebp 指针 
             ret  8                      ;  返回(相当于 ret; add esp,8 ) 
        }

三 : 其堆栈调用示意图:(编译原理的教程中说的更清楚)

四 : 例子

00401000  /$ 6A04        push    4               ; /Arg2 = 00000004

00401002  |. 6A03         push    3                ; |Arg1 = 00000003

00401004  |.  E8 16000000   call   0040101F          ; \local.0040101F

00401009  |.  8BD8         mov     ebx, eax

0040100B  |. 6A00         push    0                ; /ExitCode = 0

0040100D  \.  FF15 00204000 call    dword ptr [<&KERNEL32.ExitProces>; \ExitProcess

00401013      00            db      00

00401014      00            db      00

00401015      00            db      00

00401016      00            db      00

00401017      00            db      00

00401018      00            db      00

00401019      00            db      00

0040101A      00            db      00

0040101B      00            db      00

0040101C      00            db      00

0040101D      00            db      00

0040101E      00            db      00

0040101F  /$  55           push    ebp

00401020  |.  8BEC         mov    ebp, esp

; 使 EBP=ESP=0012FFB4

; 设置新的 EBP 指针,指向栈顶,

; 从这里开始, EBP 就作为寻址参数的基址指针。

00401022  |.  83EC 04       sub     esp, 4

; 扩展栈空间 ESP=0012FFB0

00401025  |.  8B450C       mov    eax, dword ptr [ebp+C] ;

; 当前堆栈 3 个双字偏移的数值

; ebp+C=0012FFB4+C=0012FFC0

;EAX=[0012FFC0]=00000004

00401028  |.  8B5D 08       mov     ebx, dword ptr [ebp+8]

; 当前堆栈 2 个双字偏移的数值

; ebp+8==0012FFB4+8=0012FFBC

;EBX=[0012FFBC]=00000003

0040102B  |.  895D FC      mov     dword ptr [ebp-4], ebx

;[ebp-4] 就是局部变量 =[0012FFB4-4]=[0012FFB0]

; 自动减 4, 也就是将 EBX 中的值 00000003

; 放入堆栈的 0012FFB0 地址处

; 参数 1 放局部变量里

0040102E  |.  0345 FC       add     eax, dword ptr [ebp-4]

; 将局部变量与 EAX 相加后放入 EAX 中

;EAX=00000007

; 参数 2 与局部变量相加

00401031  |. 83C4 04       add     esp, 4

;  释放局部变量占用的堆栈 ,ESP: 0012FFB4

00401034  |.  5D            pop     ebp

; 恢复原有的 EBP, ESP:0012FFB8

00401035  \.  C2 0800       retn    8

;  返回 ( 相当于 ret; add esp,8)

; ESP: 0012FFC4

堆栈的情况:

0012FFB0   00000003        ; 局部变量

0012FFB4   0012FFF0              ; 保存 EBP , push    ebp

0012FFB8   00401009       ; 压入返回地址

; 返回到 local.< 模块入口点 >+9 来自 local.0040101F

0012FFBC   00000003        ; push    3 ,每次堆栈地址加 32 位,双字

0012FFC0   00000004        ; push    4

五 : 说明

Intel 的堆栈是在内存中是向下扩展的。先进栈的数据内存地址最高,后进栈的数据内存地址减少。且数据是按小尾类型存储,例如:数值 12345678H 存放的形式(假设按字):先存 1234 ,后存放 5678

时间: 2024-10-23 22:04:30

函数参数压栈,栈帧ebp,esp怎样移动的?的相关文章

C语言函数参数压栈顺序为何是从右到左?(从左向右的话,碰到printf的会陷入死循环)

上学期学习了汇编语言,并在操作系统实验中使用了汇编+C语言混合编程,中间也了解了一些C语言与汇编语言的对应关系. 由于汇编语言是底层的编程语言,各种函数参数都要直接控制栈进行存取,在混合编程中,要用汇编来调用C函数,当然就要知道参数的压栈情况了. 当知道C函数的参数压栈顺序是从右到左时,我觉得很奇怪,因为大多数情况下,人们的习惯是从左到右的,难不成设计者学咱们中国古代写字从右到左的习惯不成? 当时只是记下了这个规则而已,并没有去探究这其中的缘由,后来在实验中自己用汇编实现了printf和scan

C之函数参数(三十九)

我们上节博文讲了函数的意义,那么我们今天来讲下函数参数.函数参数在本质上与局部变量相同在栈上分配空间,函数参数的初始值是函数调用时的实参值.用下图来实际说明 函数参数的求值顺序依赖于编译器的实现,我们来看看下面代码的输出是什么?为什么呢? #include <stdio.h> int func(int i, int j) {     printf("i = %d, j = %d\n", i, j);          return 0; } int main() {    

函数参数的压栈顺序

(Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu 转载请标明来源) 先来看一道面试题: 设int arr[]={1,2,3,4 }; int *ptr=arr; printf("%d,%d",*ptr,*(++ptr)); 面试题的答案是: 2, 2 这个面试题为什么会这样呢? 原因就跟函数的压栈顺序有关,先压栈哪一个,哪一个就会被先计算,后压栈哪一个,那一个就会被后计算. 对于常见的C++程序,像缺省_cdecl或使用_stdcall的函数压栈顺序都

函数的调用过程——栈帧。

今天我们来看一下函数的调用过程与栈帧. 我们通过一段简单的代码和图示来介绍这个过程: #include<stdio.h> int add(int x,int y) { int z = x + y; return z; } int main() { int a = 0xaaaaaaaa; int b = 0xbbbbbbbb; int c = add(a, b); printf("run here!%d\n", c); return 0; } 将这个过程用图示表示出来: 在c

C语言中函数参数为什么是由右往左入栈的?

先通过一个小程序来看一看: #include void foo(int x, int y, int z) { printf("x = %d at [%X]n", x, &x); printf("y = %d at [%X]n", y, &y); printf("z = %d at [%X]n", z, &z); } int main(int argc, char *argv[]) { foo(100, 200, 300);

C语言中函数参数入栈的顺序 - Fangzhen - 博客园

.wiz-todo, .wiz-todo-img {width: 16px; height: 16px; cursor: default; padding: 0 10px 0 2px; vertical-align: -10%;-webkit-user-select: none;} .wiz-todo-label { display: inline-block; padding-top: 7px; padding-bottom: 6px; line-height: 1.5;} .wiz-todo

C语言函数调用参数压栈的相关问题

参数入栈的顺序 以前在面试中被人问到这样的问题,函数调用的时候,参数入栈的顺序是从左向右,还是从右向左.当时没有想清楚,随口就说从右向左.其实这个回答是不完全正确的.因为其实入栈的顺序,不同的体系架构是不一样的,举例来说, 看下面的代码: #include <stdio.h> int test(int a, int b) { printf("address of a %x.\n", &a); printf("address of b %x.\n"

C函数的调用过程 &nbsp; 栈帧

C语言中,每个栈帧对应着一个未运行完的函数.栈帧中保存了该函数的返回地址和局部变量. 首先,栈是从高地址向低地址延伸的.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 先来看一个代码 #include <stdio.h> void fun() {     int tmp = 10;     int*p = (int*)(*(&tmp + 1));     *(p - 1) = 20; } int main() {     int a = 0;

JavaScript的最大函数参数长度和最大栈深度检测

也许一般代码不会触及最大参数长度和最大栈深度,但某些特殊场合,检测这两个参数还是有必要的 例如:用递归计算斐波那契数列的第n个值,不了解最大栈深度,难免显得肤浅 又例如:将一串charCode转成String,不了解最大参数长度,采用字符串拼接的方式,效率提不上,特别是在串较长的情况下 function getMaximumSupportedArgumentsLength(){ var args={length:0}; function noop(){} function test(n){ ar