V.D.
V.D.

Reputation: 1581

Pop inside procedure does not work as intended

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?

Upvotes: 1

Views: 74

Answers (1)

Sep Roland
Sep Roland

Reputation: 39166

push, call, pop, ret all impact the stack

push 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.

Solution 1

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

Solution 2

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.

Solution 3

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

Upvotes: 3

Related Questions