我正在使用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
这就是应该的工作方式。调用函数会将返回地址压入堆栈。因此,当您输入函数时,堆栈的顶部将是返回地址,而不是您之前输入的内容。
在32位代码中,您现在可以直接使用堆栈指针直接访问先前推入的值(在16位模式下类似[esp+4]
或[esp+2]
之类的东西),但是对于纯16位汇编来说这是不可能的仅具有16位寻址模式且其寄存器选择有限(不包括[sp]
)。
通常的方法是将bp
设置为frame pointer,您可以从中随机访问堆栈框架,包括堆栈args或您为其保留空间的任何本地var。
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
而不是pop dx
,我们在这里使用mov dx, [bp+4]
。此时,[bp]
将是先前的bp
值(因为它是在bp
被分配给sp
之前最后一次推送),[bp+2]
是返回地址,而[bp+4]
是您的第一个参数。
((请注意堆栈向下生长,这就是为什么您在这里需要+4
而不是-4
的原因。]
另外,返回时,必须确保将参数从堆栈中删除。您可以让调用方清除,也可以使用ret
和要删除的字节数作为参数。这是弹出地址的额外sp += n
after。在您的情况下,ret 2
将为此功能实现被调用者弹出窗口。