调用例程后访问推送的参数

Accessing pushed args after calling a routine

我正在使用 FreeDOS 和 nasm 学习 x86 汇编。我有这个小测试程序,它所做的就是将 A 打印到屏幕并退出。

如果我不使用 Write 例程,它工作正常。

但是似乎正在发生的事情是,当我将 A 压入堆栈然后调用 Write 时,它​​会将下一个 IP 压入堆栈,当我在例程中弹出 A 时,我得到的是 IP 而不是我压入的值.

我确定这很简单,但我没有看到问题所在。

segment data                    ; start of data segment

segment code                    ; start of code segment
..start:

label1:
top:
        push 'A'
        call Write
        mov ah, 4ch
        mov al, 0
        int 21h

Write:
        pop dx
        mov ah, 02h
        int 21h
        ret
end:
        mov ah, 4ch             ;exit
        mov al, 0               ;exit code 0
        int 21h                 ;call intr

segment stack class=stack       ; start of stack segment
        resb 512

这就是应该的工作方式。调用函数会将 return 地址压入堆栈。因此,当您的函数被输入时,堆栈顶部将是 return 地址,而不是您之前推送的地址。

在 32 位代码中,您现在可以直接使用堆栈指针来访问先前推送的值(类似于 16 位模式中的 [esp+4][esp+2]),但这不是可能只有 16 位寻址模式的纯 16 位汇编及其有限的寄存器选择(不包括 [sp])。

通常的方法是将 bp 设置为 frame pointer,您可以从中随机访问堆栈框架,包括堆栈参数或您为 space 保留的任何本地变量.

Write:
    push bp            ; Save previous value of bp so it won't get lost
    mov bp, sp         ; Set bp ("base pointer") to current stack pointer position

    mov dx, [bp+4]     ; Get argument from stack
    mov ah, 02h
    int 21h

    mov sp, bp         ; Restore stack pointer
    pop bp             ; Restore value of base pointer

    ret 2              ; Indicate how many bytes should be popped from stack after return

我们在这里使用 mov dx, [bp+4] 而不是 pop dx。此时,[bp] 将是先前的 bp 值(因为它是在 bp 分配给 sp 之前最后一次推送),[bp+2] 将是 return 地址,[bp+4] 你的第一个参数。

(记住堆栈向下增长,这就是为什么你需要 +4 而不是 -4 的原因。)

此外,当您 return 时,您必须确保参数已从堆栈中移除。您可以让调用者清理或使用 ret 和要删除的字节数作为参数。这是一个额外的 sp += n after 弹出 return 地址。在您的情况下,ret 2 将为此函数实现 callee-pops。