x86 assembly: Pass parameter to a function through stack

16,813

Solution 1

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

Solution 2

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

Share:
16,813
sergiu reznicencu
Author by

sergiu reznicencu

Updated on June 17, 2022

Comments

  • sergiu reznicencu
    sergiu reznicencu almost 2 years

    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
    
  • sergiu reznicencu
    sergiu reznicencu almost 8 years
    Isn't BP already used in the subprogram for I don;t know what address? 'The 16-bit BP register mainly helps in referencing the parameter variables passed to a subroutine. The address in SS register is combined with the offset in BP to get the location of the parameter. BP can also be combined with DI and SI as base register for special addressing.' (from tutorialspoint.com/assembly_programming/assembly_registers.h‌​tm)
  • sergiu reznicencu
    sergiu reznicencu almost 8 years
    Why [BP+6]? I know [] are used when referring to an address..and if BP is the address of the subroutine (I guess) then [BP+6] will point to a command from the subroutine right? (I'm kinda new so I can be wrong..). And why 6? (I know that +1 means for example the next address that can point to a var or something else.)
  • sergiu reznicencu
    sergiu reznicencu almost 8 years
    So ..2 actually means 2 bytes right?...jump over 2 bytes not an entire address(I though +1 means jump over an whole address whatever it's lenght in bytes is..).
  • Matteo Italia
    Matteo Italia almost 8 years
    BP is not the address of the subroutine, it's the address of the position of the stack where the old value of BP is stored; all those offset relative to BP are just offsets in the stack, relative to where the stack was when I copied SP in BP. All the "2"s are because each push in 16 bit mode pushes a 2-byte entry on the stack.
  • sergiu reznicencu
    sergiu reznicencu almost 8 years
    As I said..I'm kinda new...what does ' address of the position of the stack where the old value of BP is stored' mean?
  • Matteo Italia
    Matteo Italia almost 8 years
    See it like this; the current stack position is the location in memory that is pointed to by sp; when you do a push ax, you are actually doing mov [sp], ax (move ax into the memory location pointed by sp - similar to a pointer dereference in C) plus a sub sp,2. Now, with bp you are just taking a snapshot of where sp was when you entered the function - the mov bp, sp does just that. Then, using mov [bp+something] you can refer to positions in the stack relative to where the stack was when you entered the function.
  • Matteo Italia
    Matteo Italia almost 8 years
    For some more detail and nice diagrams, you should look up "x86 stack frame" (although most material is about 32 bit stack frames, it's essentially the same).
  • sergiu reznicencu
    sergiu reznicencu almost 8 years
    Thanks...I understand now..with 32 bits it would be +4 no?
  • sergiu reznicencu
    sergiu reznicencu almost 8 years
    By subtraction could SP get to 0?
  • Matteo Italia
    Matteo Italia almost 8 years
    +4: yes, it all gets multiplied by two; SP to 0: in theory yes, but typically it starts overwriting other important stuff before getting there (or, on a modern operating system, the OS kills the process when the stack goes outside its given boundaries - typically by placing some guard page at the end of the stack).
  • Sep Roland
    Sep Roland almost 8 years
    @MatteoItalia Your answer has a few errors. You wrote: "In this schema [ebp+something] typically is a local, [ebp-something] is a parameter." The signs needs to be inversed! Locals use negative offsets, parameters use positive offsets.
  • Matteo Italia
    Matteo Italia almost 8 years
    @SepRoland: whops, sorry, I thought I fixed it throughout the answer but I missed that point. Fixed now, thank you!
  • Peter Cordes
    Peter Cordes almost 8 years
    Note that the OP's idea does actually barely work, for simple functions with caller-pops. I added an answer to point this out. It's interesting when a newbie identifies a non-standard way to do something that turns out to actually work, even if it's terrible. :P