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

广告位

汇编里把一段内存空间定义为一个栈,栈总是先进后出,栈的最大空间为 64K。由于 "栈" 是…

汇编里把一段内存空间定义为一个栈,栈总是先进后出,栈的最大空间为 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

关于作者: 汇编语言

为您推荐

广告位

发表评论