I have next code in TASM:
.MODEL SMALL
.STACK 128
.DATA
msg DB 'Message$'
crlf DB 0Dh, 0Ah, '$'
.CODE
print_string proc
pop dx
mov ah, 09h
int 21h
ret
print_string endp
Entry:
mov ax, @data
mov ds, ax
push offset msg
call print_string
mov ax, 4c00h
int 21h
END Entry
I have a procedure that retrieves a message pointer from the stack for printing, but it prints weird symbols into console.
If I inline my procedure into code like this:
push offset msg
pop dx
mov ah, 09h
int 21h
it will work correctly and output my message into console.
Can someone explain why popping the same value from stack inside procedure results in non-expected behaviour?
push
, call
, pop
, ret
all impact the stackpush offset msg call print_string
After these 2 instructions have been executed the stack looks like:
R, R, M, M, ... R = Return address (2 bytes)
^ M = Message pointer (2 bytes)
sp
The instruction that runs next is that pop dx
. The stack will now look like:
M, M, ...
^
sp
Contrary to what you intended, the DX register now contains the return address for the call
/ret
instructions. That's not a valid pointer to a message and so you see garbage appear on the screen.
And when then the ret
instruction runs, it will pop off the stack some word that isn't a return address at all.
Move the return address temporarily away. Next code pops it into AX that we're going to clobber anyway:
print_string proc
pop ax ; return address
pop dx ; message pointer (removes the argument from the stack)
push ax ; return address
mov ah, 09h
int 21h
ret
print_string endp
Use a stackframe pointer.
print_string proc
push bp ; preserving BP
mov bp, sp
mov dx, [bp+4] ; message pointer
mov ah, 09h
int 21h
pop bp ; restoring BP
ret 2 ; remove the argument from the stack
print_string endp
Because we want to preserve BP, we used the push bp
instruction which made the stack look like:
B, B, R, R, M, M, ...
^ ^
sp |
bp |
<--- +4 --->|
We retrieve the message pointer at offset +4 from the new stackpointer.
Use a stackframe pointer.
print_string proc
xchg bp, ax ; preserving BP in the AX register
mov bp, sp
mov dx, [bp+2] ; message pointer
xchg bp, ax ; restoring BP from the AX register
mov ah, 09h
int 21h
ret 2 ; remove the argument from the stack
print_string endp
Here we preserve BP in the AX register that the code is going to clobber anyway. The stack looks like:
R, R, M, M, ...
^ ^
sp |
bp |
< +2 >|
We retrieve the message pointer at offset +2 from the new stackpointer.
The last examples used ret 2
to remove the stacked argument. Alternatively, you could remove the argument once returned to the caller:
push offset msg
call print_string
add sp, 2