【问题标题】:Emu8086 pusha popa鸸鹋8086
【发布时间】:2017-03-23 08:57:29
【问题描述】:

我正在寻找一种在汇编程序中使用 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”时,我们将当前所在的地址压入堆栈。 还有其他简单的方法吗?我不想每次都写 8 push 和 8 pop。

【问题讨论】:

  • emu8086 支持宏吗?
  • "如果你声明了一个宏并且从未在你的代码中使用过它,编译器会简单地忽略它。emu8086.inc 是如何使用宏的一个很好的例子,这个文件包含几个宏来进行编码对你来说更容易。”
  • 好像支持宏。

标签: assembly emu8086


【解决方案1】:

可以将程序实现为宏

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

【讨论】:

  • 这是一个很好的答案!
  • “如果我的数学计算正确 - 请仔细检查 - 这段代码应该可以解决问题” 遗憾的是,这是错误的。堆栈向下增长,因此数学必须相反。 sub bp, 0ah xchg bp, [bp+0ah] >>> add bp, 0ah xchg bp, [bp-0ah]
  • 你完全忽略了这个替换代码是pusha/popa不改变EFLAGSadd bp, 10 最好变成 lea bp, [bp+10]
  • @Fifoernik 你是对的!非常感谢您的反馈!那个 sn-p 确实完全坏了。
【解决方案2】:

我正在寻找一种在汇编程序中简单的 pusha popa 方法。

我不想每次都写8个push和8个pop。

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

虽然@MargaretBloom 的回答很好地解释了push sppop sp 的所有问题,但它并没有为您提供最简单的解决方案。此外,在保留和恢复所有通用寄存器的情况下,压入和弹出堆栈指针是一个愚蠢的操作。甚至制造商(英特尔)也知道这一点,并在执行popa 时巧妙地绕过了推送的SP 值。 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 将无法运行。

【讨论】:

    【解决方案3】:
    pushuj    MACRO  
        push ax
        push cx
        push dx
        push bx
        push sp
        push bp
        push si
        push di
    
    ENDM
    

    当我想称呼它时,我会写:

    pushuj
    

    我好吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-15
      • 2015-07-31
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多