压栈一次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