Emu8086 抽汽水

Emu8086 pusha popa

我正在寻找一种在汇编程序中 pusha popa 的简单方法。我想使用 emu8086 查找程序中的错误,因此不允许使用 .286。 我试过了:

push_a proc
push ax
push cx
push dx
push bx
push sp
push bp
push si
push di
push_a endp

pop_a proc
pop di
pop si
pop bp
pop sp
pop bx
pop dx
pop cx
pop ax
pop_a endp

但它不起作用,因此当我们 "call push_a" 时,我们将我们现在所在的地址压入堆栈。 还有其他简单易行的方法吗?不想每次都写八push八pop

pushuj    MACRO  
    push ax
    push cx
    push dx
    push bx
    push sp
    push bp
    push si
    push di

ENDM

当我想调用它时,我写:

pushuj

我这样好吗?

可以将过程作为宏来实现

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 宏的开始

Temp ← (SP);
Push(AX);
Push(CX);
Push(DX);
Push(BX);
Push(Temp);
Push(BP);
Push(SI);
Push(DI);

                                                                                             Intel pseudo-code for pusha - Intel Manual 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

非常感谢 通过注意反转数学并防止更改 EFLAGS 寄存器来更正损坏的代码。

上面代码片段的优点在于它完全避免使用 push sp,因为它在向后兼容性方面有点问题。

push sp

的问题

在真正的 8086 上,指令 push sp 的行为不同:

The P6 family, Pentium, Intel486, Intel386, and Intel 286 processors push a different value on the stack for a PUSH SP instruction than the 8086 processor.
The 32-bit processors push the value of the SP register before it is decremented as part of the push operation;
the 8086 processor pushes the value of the SP register after it is decremented.

                                                                                             Intel compatibility section - Intel Manual 3 - 22.17

我不知道 emu8086 作为 8086 的仿真器有多准确,但我会避免 push sp

popa

的语义

pusha 的特殊实现方式需要 popa 的特殊实现方式。
在真正的 8086 上,pop 的原始列表在 pop sp 执行后立即中断。

事实上,CPU 利用了这样一个事实,即即使在 pusha 之后,您也无法真正修改 sp - 否则将无法取回这些值 - 并注意到在所有 pop 之后,原始值被隐式恢复。

DI ← Pop();
SI ← Pop();
BP ← Pop();
Increment ESP by 2; (* Skip next 2 bytes of stack *)
BX ← Pop();
DX ← Pop();
CX ← Pop();
AX ← Pop();

                                                                                             Intel pseudo-code for popa - Intel Manual 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 不是幂等的,因为

The POP ESP instruction increments the stack pointer (ESP) before data at the old top of stack is written into the destination.

                                                                                             Intel description for pop - Intel Manual 2B

最后,人们会期望 pusha/popa 在中断方面是原子的,因此在宏主体周围需要一对 cli/sti


如果您使用 pusha/popa 作为 shorthand 来保存所有的寄存器,那么所有的麻烦都可以跳过并且一个充分的实现是

 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

I'm looking for an easy way of pusha popa in assembly program.

I don't want to write eight push and eight pop every time.

这正是您在使用建议的 MACRO 解决方案时要做的事情!每次使用宏的名称时,它所代表的整个代码块都会被替换。这真是浪费space。

尽管@MargaretBloom 的回答很好地解释了 push sppop sp 的所有问题,但它并没有为您提供最简单的解决方案。此外,在保留和恢复所有通用寄存器的上下文中,压入和弹出堆栈指针是一种愚蠢的操作。甚至制造商(英特尔)也知道这一点,并在执行 popa 时巧妙地绕过了推送的 SP 值。 SP寄存器被pusha压入只是因为它简化了硬件设计。

满足要求的最佳解决方案是在推送/弹出 7 个有用的 GPR 之前暂时从 call push_a / call pop_a 中删除 return 地址。在执行 ret 之前,return 地址被放回堆栈。瞧。

push_a proc
    pop word ptr [TEMP]
    push ax
    push cx
    push dx
    push bx
    push bp
    push si
    push di
    push word ptr [TEMP]
push_a endp

pop_a proc
    pop word ptr [TEMP]
    pop di
    pop si
    pop bp
    pop bx
    pop dx
    pop cx
    pop ax
    push word ptr [TEMP]
pop_a endp

此解决方案也不会更改其操作的任何标志。被@Fifoernik观察到。
这是一个想法:为什么不将 FLAGS 保留为第八个寄存器?

最后一点。要使此代码真正健壮,您可以将此 TEMP 变量放在程序的代码段中,并使用显式 CS: 覆盖前缀对其进行寻址。当您需要使用新的 push_a / pop_a[ 时,您的 DS 段寄存器可能并不总是位于正确的位置=41=]来电。您的 CS 寄存器本来就是正确的。否则,call 甚至无法运行。