2025年4月7日 星期一 乙巳(蛇)年 正月初八 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > 汇编

汇编语言PUSH和POP指令(压栈和出栈)

时间:03-05来源:作者:点击数:92

汇编里把一段内存空间定义为一个栈,栈总是先进后出,栈的最大空间为 64K。由于 "栈" 是由高到低使用的,所以新压入的数据的位置更低,ESP 中的指针将一直指向这个新位置,所以 ESP 中的地址数据是动态的。

PUSH 指令

PUSH 指令首先减少 ESP 的值,再将源操作数复制到堆栈。操作数是 16 位的,则 ESP 减 2,操作数是 32 位的,则 ESP 减 4。PUSH 指令有 3 种格式:

PUSH reg/mem16
PUSH reg/mem32
PUSH inm32

POP指令

POP 指令首先把 ESP 指向的堆栈元素内容复制到一个 16 位或 32 位目的操作数中,再增加 ESP 的值。如果操作数是 16 位的,ESP 加 2,如果操作数是 32 位的,ESP 加 4:

POP reg/mem16
POP reg/mem32

PUSHFD 和 POPFD 指令

PUSHFD 指令把 32 位 EFLAGS 寄存器内容压入堆栈,而 POPFD 指令则把栈顶单元内容弹出到 EFLAGS 寄存器:

pushfd
popfd

不能用 MOV 指令把标识寄存器内容复制给一个变量,因此,PUSHFD 可能就是保存标志位的最佳途径。有些时候保存标志寄存器的副本是非常有用的,这样之后就可以恢复标志寄存器原来的值。通常会用 PUSHFD 和 POPFD 封闭一段代码:

pushfd           ;保存标志寄存器;
;
任意语句序列
;
popfd           ;恢复标志寄存器

当用这种方式使用入栈和出栈指令时,必须确保程序的执行路径不会跳过 POPFD 指令。当程序随着时间不断修改时,很难记住所有入栈和出栈指令的位置。因此,精确的文档就显得至关重要!

一种不容易出错的保存和恢复标识寄存器的方法是:将它们压入堆栈后,立即弹出给一个变量:

  • .data
  • saveFlags DWORD ?
  • .code
  • pushfd ;标识寄存器内容入栈
  • pop saveFLags ;复制给一个变量

下述语句从同一个变量中恢复标识寄存器内容:

  • push saveFlags ;被保存的标识入栈
  • popfd ;复制给标识寄存器

PUSHAD,PUSHA,POPAD 和 POPA

PUSHAD 指令按照 EAX、ECX、EDX、EBX、ESP(执行 PUSHAD 之前的值)、EBP、ESI 和 EDI 的顺序,将所有 32 位通用寄存器压入堆栈。

POPAD 指令按照相反顺序将同样的寄存器弹出堆栈。与之相似,PUSHA 指令按序(AX、CX、DX、BX、SP、BP、SI 和 DI)将 16 位通用寄存器压入堆栈。

POPA 指令按照相反顺序将同样的寄存器弹出堆栈。在 16 位模式下,只能使用 PUSHA 和 POPA 指令。

如果编写的过程会修改 32 位寄存器的值,则在过程开始时使用 PUSHAD 指令,在结束时使用 POPAD 指令,以此保存和恢复寄存器的内容。示例如下列代码段所示:

  • MySub PROC
  • pushad ;保存通用寄存器的内容
  • .
  • .
  • mov eax,...
  • mov edx,...
  • mov ecx,...
  • .
  • .
  • popad ;恢复通用寄存器的内容
  • ret
  • MySub ENDP

必须要指岀,上述示例有一个重要的例外:过程用一个或多个寄存器来返回结果时,不应使用 PUSHA 和 PUSHAD。假设下述 ReadValue 过程用 EAX 返回一个整数;调用 POPAD 将会覆盖 EAX 中的返回值:

  • ReadValue PROC
  • pushad ;保存通用寄存器的内容
  • .
  • .
  • mov eax rreturn_value
  • .
  • .
  • popad ;覆盖 EAX !
  • ret
  • ReadValue ENDP

示例:字符串反转

现在查看名为 RevStr 的程序:在一个字符串上循环,将每个字符压入堆栈,再把这些字符从堆栈中弹出(相反顺序),并保存回同一个字符串变量。由于堆栈是 LIFO(后进先出)结构,字符串中的字母顺序就发生了翻转:

  • ;字符串翻转(Revstr.asm)
  • .386
  • .model flat,stdcall
  • .stack 4096
  • ExitProcess PROTO,dwExitCode:DWORD
  • .data
  • aName BYTE "Abraham Lincoln",0
  • nameSize = ($-aName)-1
  • .code
  • main PROC
  • ;将名字压入堆栈
  • mov ecx,nameSize
  • mov esi,0
  • L1: movzx eax,aName[esi] ;获取字符
  • push eax ;压入堆栈
  • inc esi
  • loop L1
  • ;将名字逆序弹出堆栈
  • ;并存入aName数组
  • mov ecx,nameSize
  • mov esi,0
  • L2: pop eax ;获取字符
  • mov aName[esi],al ;存入字符串
  • inc esi
  • loop L2
  • INVOKE ExitProcess,0
  • main ENDP
  • END main
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门