可以将程序实现为宏
pusha MACRO
push ax
push cx
push dx
push bx
push sp
push bp
push si
push di
pusha ENDM
popa MACRO
pop di
pop si
pop bp
pop sp
pop bx
pop dx
pop cx
pop ax
popa ENDM
但这不是pusha/popa 的架构行为,它是真正的 8086 上的错误代码。
pusha的真正语义
pusha 中的push sp 应该在pusha 宏的开始处推送堆栈指针的状态。
温度 ← (SP);
推(AX);
推送(CX);
推(DX);
推(BX);
推(温度);
推(BP);
推(SI);
推(DI);
英特尔伪代码pusha-英特尔手册2B
使用暂存位置
如果你有一个临时内存位置,你可以做类似的事情:
pusha MACRO
mov WORD PTR [pusha_scratch], sp
push ax
push cx
push dx
push bx
push WORD PTR [pusha_scratch]
push bp
push si
push di
pusha ENDM
确保在任何情况下都可以通过DS 访问pusha_scratch 是一项艰巨的任务。
不考虑 pusha_scratch 的单独定义。
使用位置独立的就地版本
或者,可以使用与位置无关且就地操作的东西来解释 sp 的修改值。
如果我的数学计算正确 - 仔细检查 - 这段代码应该可以解决问题
pusha MACRO
push ax
push cx
push dx
push bx
push bp ;Top of stack = BP
mov bp, sp ;BP points to TOS
lea bp, [bp+0ah] ;BP is equal to the starting value of SP
xchg bp, [bp-0ah] ;BP = Original BP, [SP] = Starting value of BP
push bp
push si
push di
pusha ENDM
非常感谢 Fifoernik 通过注意倒置数学并防止更改 EFLAGS 寄存器来纠正损坏的代码。
上面的 sn-p 的好处是它完全避免使用push sp,因为它在向后兼容性方面有点问题。
push sp的问题
在真正的 8086 上,指令 push sp 的行为不同:
P6 系列、Pentium、Intel486、Intel386 和 Intel 286 处理器在堆栈上推送的 PUSH SP 指令值与 8086 处理器不同。
作为压入操作的一部分,32 位处理器在减量之前压入 SP 寄存器的值;
8086 处理器在减量后压入 SP 寄存器的值。 p>
英特尔兼容性部分 - 英特尔手册 3 - 22.17
我不知道 emu8086 作为 8086 的模拟器有多准确,但我会避免使用 push sp。
popa的语义
pusha 的特殊实现方式需要 popa 的特殊实现。
在真正的 8086 上,pops 的幼稚列表会在 pop sp 执行后立即中断。
实际上,CPU 利用了这样一个事实,即即使在 pusha 之后,您也无法真正修改 sp - 否则将无法取回这些值 - 并注意到毕竟 pops 原来的值被隐式恢复。
DI ← Pop();
SI ← Pop();
BP ← Pop();
将 ESP 增加 2; (* 跳过堆栈的下 2 个字节 *)
BX ← Pop();
DX ← Pop();
CX ← Pop();
AX ← Pop();
英特尔伪代码popa-英特尔手册2B
所以popa 的更正确实现是
popa MACRO
pop di
pop si
pop bp
add sp, 02h ;Beware, this affects EFLAGS
pop bx
pop dx
pop cx
pop ax
popa ENDM
注意您必须避免在真正的 8086 中使用 pop sp,因为 push sp 后跟 pop sp 不是幂等的,因为
POP ESP 指令在将旧堆栈顶部的数据写入堆栈指针之前递增堆栈指针 (ESP)
目的地。
英特尔对pop的描述-英特尔手册2B
最后,人们会期望pusha/popa 在中断方面是原子的,因此在宏主体周围需要一对cli/sti。
如果你使用pusha/popa作为保存所有寄存器的简写,那么所有的麻烦都可以跳过,一个足够的实现是
push_all MACRO
push ax
push cx
push dx
push bx
;NOTE: Missing SP
push bp
push si
push di
push_all ENDM
pop_all MACRO
pop di
pop si
pop bp
;NOTE: Missing SP
pop bx
pop dx
pop cx
pop ax
pop_all ENDM