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 sp
和 pop 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
甚至无法运行。
我正在寻找一种在汇编程序中 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
非常感谢
上面代码片段的优点在于它完全避免使用 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 sp
和 pop 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
甚至无法运行。