I am learning x86 assembly using FreeDOS and nasm. I have this little test program that all it does is print A to the screen and exits.
It works fine if I don't use the Write routine.
But what seems to be happening is when I push A on to the stack then call Write it places the next IP on to the stack and when I pop off A in the routine I get the IP not the value I pushed.
I am sure it is something simple but I don't see the issue.
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
This is how it is supposed to work. Calling into a function pushes the return address on the stack. So, when your function is entered, the top of the stack will be return address and not what you previously pushed.
In 32-bit code you could now just use the stack pointer directly to access the previously pushed value (something like [esp+4]
or [esp+2]
in 16-bit mode), but this is not possible with pure 16-bit assembly with only 16-bit addressing modes and their limited choice of registers (not including [sp]
).
The normal way is to set up bp
as a frame pointer from which you have random access to your stack frame, including stack args or any local vars you reserve space for.
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
Instead of pop dx
, we use mov dx, [bp+4]
here. At this point, [bp]
would be the previous bp
value (since it was last pushed before bp
was assigned to sp
), [bp+2]
would be the return address, and [bp+4]
your first argument.
(Remember that the stack grows downwards, that's why you need +4
and not -4
here.)
Also, when you return, you have to be sure that the argument is removed from the stack. You can either let the caller clean up or use ret
with the number of bytes to remove as argument. It's an extra sp += n
after popping the return address. In your case, ret 2
would implement callee-pops for this function.