Reputation: 1039
I'm trying to make a subprogram in assembly which will draw a square on the screen. I don't think I can pass parameters to the subprogram like I would do in C++, so I figured that I could use stack to store and access the parameters (I can't use the common data registers because there are too many variables to pass).
The problem is (I remember reading somewhere) that when I use the call command to the address of the current "program" it is saved on the stack, so that when it's used the "ret" command it would know where to return. But if I store something on the stack and then call the function, I will have to save somewhere the address (that is on the top of stack) and then safely pop the parameters. Then after the code has finished and before calling "ret", I would have to push back the address.
Am I right? And, if yes, where can I store the address (I don't think the address is only 1 byte long so that it would fit in AX or BX or any other data register). Can I use IP to do this (although I know this is used for something else)?
This is what I imagine:
[BITS 16]
....
main:
mov ax,100b
push ax
call rectangle ;??--pushes on the stack the current address?
jml $
rectangle:
pop ax ;??--this is the addres of main right(where the call was made)?
pop bx ;??--this is the real 100b, right?
....
push ax
ret ;-uses the address saved in stack
Upvotes: 7
Views: 24024
Reputation: 17
I don't think I can pass parameters to the subprogram like I would do in C++ [...]
To pass parameters to a subroutine you can do the following trick as seen in the example below:
.486
assume cs:code, ds:data, ss:stack
macro_for_subroutine macro parameter1, parameter2
push parameter1 ; [bp+6]
push parameter2 ; [bp+4]
call subroutine ; [bp+2] (return address pushed onto the stack)
endm
stack segment use16 para stack
db 256 dup(' ')
stack ends
data segment use16
value1 dw 0
value2 dw 0
data ends
code segment use16 para public 'code'
start:
main proc far
; set up stack for return
push ds
mov ax, 0
push ax
; ----
; set DS register to data segment
mov ax, data
mov ds, ax
macro_for_subroutine 1111h, 2222h
ret ; return to DOS
main endp
subroutine proc near
push bp ; [bp+0]
mov bp, sp
push ax
push bx
mov ax, [bp+6] ; parameter1
mov value1, ax
mov bx, [bp+4] ; parameter2
mov value2, bx
pop bx
pop ax
pop bp
ret 4 ; return and then increase SP by 4, because we
; pushed 2 parameters onto the stack from the macro
subroutine endp
code ends
end start
Note: This is written in 16-bit MASM DOS assembly.
Macros can accept parameters. Therefore, by defining a macro for a specific subroutine, you can simulate a call to a subroutine with parameters. Inside the macro, you push onto the stack the parameters in the desired order and then make a call to the subroutine.
You can't pass string variables, but you can pass their offset (for more, see: x86 assembly - masm32: Issues with pushing variable to stack).
Upvotes: 0
Reputation: 363999
This would work, except that from the caller's perspective, your function modifies sp
. In 32bit most calling conventions, functions are only allowed to modify eax/ecx/edx
, and must save/restore other regs if they want to use them. I assume 16bit is similar. (Although of course in asm you can write functions with whatever custom calling conventions you like.)
Some calling conventions expect the callee to pop the args pushed by the caller, so this would actually work in that case. The ret 4
in Matteo's answer does that. (See the x86 tag wiki for info on calling conventions, and tons of other good links.)
It's super-weird, and not the best way to do things, which is why it isn't normally used. The biggest problem is that it only gives you access to the parameters in order, not random access. You can only access the first 6 or so args, because you run out of registers to pop them into.
It also ties up a register holding the return address. x86 (before x86-64) has very few registers, so this is Really Bad. You could push the return address after popping the other function args into registers, I guess, to free it up for use.
jmp ax
would technically work instead of push
/ret
, but this defeats the return-address predictor, slowing down future ret
instructions.
But anyway, making a stack frame with push bp
/ mov bp, sp
is universally used in 16bit code because it's cheap and gives you random-access to the stack. ([sp +/- constant]
isn't a valid addressing mode in 16 bit (but it is in 32 and 64bit). ([bp +/- constant]
is valid). Then you can re-load from them whenever you need.
In 32 and 64bit code, it's common for compilers to use addressing modes like [esp + 8]
or whatever, instead of wasting instructions and tying up ebp
. (-fomit-frame-pointer
is the default). It means you have to keep track of changes to esp
to work out the right offset for the same data in different instructions, so it's not popular in hand-written asm, esp in tutorials / teaching material. In real code you obviously do whatever is most efficient, because if you were willing to sacrifice efficiency you'd just use a C compiler.
Upvotes: 3
Reputation: 126777
Typically, you use the base pointer (bp
on 16 bit, ebp
on 32 bit) to refer to parameters and locals.
The basic idea is that every time you enter into a function you save the stack pointer inside the base pointer, to have the stack pointer at when the function was called as a "fixed reference point" throughout execution of the function. In this schema [ebp-something]
typically is a local, [ebp+something]
is a parameter.
Transposing the typical 32-bit, callee-cleanup calling conventions you can do like this:
caller:
push param1
push param2
call subroutine
subroutine:
push bp ; save old base pointer
mov bp,sp ; use the current stack pointer as new base pointer
; now the situation of the stack is
; bp+0 => old base pointer
; bp+2 => return address
; bp+4 => param2
; bp+6 => param1
mov ax,[bp+4] ; that's param2
mov bx,[bp+6] ; that's param1
; ... do your stuff, use the stack all you want,
; just make sure that by when we get here push/pop have balanced out
pop bp ; restore old base pointer
ret 4 ; return, popping the extra 4 bytes of the arguments in the process
Upvotes: 14