Emu8086 pusha popa

时间:2017-03-23 08:57:30

标签: assembly emu8086

我在装配程序中寻找一种简单的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"我们将堆栈地址推送到现在的位置。 还有另一种简单易行的方法吗?我不想每次都写八次推八次弹。

3 个答案:

答案 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 sppop 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甚至不起作用。