我在装配程序中寻找一种简单的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
但它不起作用,因此当我们调用push_a"我们将堆栈地址推送到现在的位置。 还有另一种简单易行的方法吗?我不想每次都写八次推八次弹。
答案 0 :(得分:6)
可以将程序实现为宏
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
push sp
中的pusha
应该在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
确保pusha_scratch
可以通过DS
在任何情境下访问,这是一项非常努力的工作。
不考虑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寄存器来纠正损坏的代码。
上面代码段的优点在于它避免完全使用push sp
,因为在向后兼容性方面有点问题。
push sp
在真实的8086上,指令push sp
的行为有所不同:
P6系列,奔腾,Intel486,Intel386和Intel 286处理器在堆栈上为
PUSH SP
指令推送的值不同于8086处理器。
在作为推送操作的一部分递减之前,32位处理器会推送SP寄存器的值;
8086处理器在递减后推送SP寄存器的值。Intel兼容性部分 - Intel手册3 - 22.17
我不知道emu8086作为8086的模拟器有多准确,但我会避免使用push sp
。
popa
实施pusha
的特殊方式要求popa
的特殊实施。
在pop
执行pop sp
时,在真正的8086上有一个简短的sp
中断列表。
事实上,CPU利用了这样一个事实:即使在pusha
之后你也无法真正修改pop
- 否则将无法获得这些值 - 并注意到popa
之后popa
1}} s隐式恢复原始值。
DI←Pop();
SI←Pop();
BP←Pop();
增加ESP 2; (*跳过下一个2字节的堆栈*)
BX←Pop();
DX←Pop();
CX←Pop();
AX←Pop();
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
的英特尔伪代码 - 英特尔手册2B
因此pop sp
的更正确的实现是
push sp
注意您必须避免在真实的8086中pop sp
作为POP ESP
后跟pop
并不是幂等的
pusha
指令增加堆栈指针(ESP),然后将旧堆栈顶部的数据写入 目的地。英特尔对
popa
的说明 - 英特尔手册2B
最后,人们希望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
/ {{1}}作为保存所有寄存器的简写,那么可以跳过所有麻烦并且有足够的实现
{{1}}
答案 1 :(得分:0)
pushuj MACRO
push ax
push cx
push dx
push bx
push sp
push bp
push si
push di
ENDM
当我想打电话给我时,我写道:
pushuj
我很好吗?
答案 2 :(得分:0)
我在装配程序中寻找一种简单的pusha popa方式。
和
我不想每次都写8次弹出和8次弹跳。
这正是您在使用建议的MACRO解决方案时将要做的事情!每次使用宏的名称时,它所代表的整个代码块都将被替换。这真的是浪费空间。
虽然@MargaretBloom的答案很好地解释了push sp
和pop sp
的所有问题,但它并没有为您提供最简单的解决方案。此外,在保留和恢复所有通用寄存器的上下文中,推送和弹出堆栈指针是一个愚蠢的操作。即使是制造商(英特尔)也知道这一点,并在执行SP
时巧妙地绕过推送的popa
值。 SP
寄存器仅由pusha
推送,因为它简化了硬件设计。
满足要求的最佳解决方案会在推送/弹出7个有用的GPR之前暂时从call push_a
/ call pop_a
删除返回地址。在执行ret
之前,返回地址将重新放回堆栈。瞧。
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
段注册可能并不总是在正确的位置。您的CS
注册本身就是正确的。如果不是,则call
甚至不起作用。