汇编语言PUSH和POP指令(压栈和出栈)
汇编里把一段内存空间定义为一个栈,栈总是先进后出,栈的最大空间为 64K。由于 "栈" 是由高到低使用的,所以新压入的数据的位置更低,ESP 中的指针将一直指向这个新位置,所以 ESP 中的地址数据是动态的。
一种不容易出错的保存和恢复标识寄存器的方法是:将它们压入堆栈后,立即弹出给一个变量:
POPAD 指令按照相反顺序将同样的寄存器弹出堆栈。与之相似,PUSHA 指令按序(AX、CX、DX、BX、SP、BP、SI 和 DI)将 16 位通用寄存器压入堆栈。
POPA 指令按照相反顺序将同样的寄存器弹出堆栈。在 16 位模式下,只能使用 PUSHA 和 POPA 指令。
如果编写的过程会修改 32 位寄存器的值,则在过程开始时使用 PUSHAD 指令,在结束时使用 POPAD 指令,以此保存和恢复寄存器的内容。示例如下列代码段所示:
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
pushfd ;保存标志寄存器
;
;任意语句序列
;
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