Parameter Passing and Stack Frames
http://asm.tsx.org

In this article, I hope to show how parameters are passed to function in assembly language, as well as how they are addressed from inside the function. I will also describe stack frames, local stack variables, and how to use them. Finally, I will show how all this info is useless, as your assembler can do it for you all automatically :)

Parameter Passing Conventions

A parameter passing convention defines how arguments are passed to a function. In the Win32 API, two different conventions are used: stdcall and C. Stdcall is used for every API function, except for wsprintf, which uses the C calling convention.

Sdtcall calling convention
In the sdtcall convention, function arguments are passed from right to left. From an assembly language standpoint, this means that the parameters are pushed onto the stack in reverse order. Also, with stdcall, the called function is responsible for cleaning up the stack upon return from the function. What this implies is that the number of arguments to a stdcall function is not variable.

  ; Example of calling a stdcall function:
  ; MessageBeep prototype: 
  ; int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
      push    MB_OK             ; uType 
      push    offset szCaption  ; lpCaption
      push    offset szText     ; lpText
      push    [hWnd]            ; hWnd
      call    MessageBoxA
It can be seen that the parameters are passed in reverse, and that there is no need adjust the stack manually. Note that all Win32 API functions (except wsprintf) use this convention.

C calling convention
In the C calling convention, function arguments are passed from right to left, as with stdcall. It differs however, by the fact that the caller of the function, not the function itself, is responsible for stack cleanup. This then implies that you can pass a variable number of arguments to a C convention function, as you can then clean the stack up correctly. Because of this capability, the API function wsprintf is defined to use the C convention. Note that C convention functions result in slightly more code, since each time the function is called, the caller has to then clean the stack manually.

  ; Example of calling a C function:
  ; wsprintf prototype: int cdecl wsprintf(LPTSTR  lpOut, LPCTSTR  lpFmt, ...);
      push    345h               ; vararg 2
      push    1                  ; vararg 1
      push    offset szFormat    ; lpFmt
      push    offset szOutput    ; lpOut 
      call    wsprintf         
      add     esp, 16            ; clean stack
Here we see that we can pass as many arguments as we like. However we must also clean the stack ourselves. In this example, 4 DWORD arguments were passed, so we have to add 4 * sizeof(DWORD), which equalss 16, to the stack pointer ESP.

Stack Frames

Stack Frames are used by high-level languages to handle parameter passing and local (stack) variables. It provides a simple method for addressing the parameters and variables. The following diagram shows a memory map for a typical function.

In this diagram, EBP points to the address 00640010. You can see that local variables can be accessed as negative offsets from this address. Likewise, passed function parameters can be accessed as positive offsets from it.

To get the values of the passed parameters or of the local variables, all that needs to be done is the following:


mov eax, dword ptr [ebp - 4] ; Local 1
mov eax, dword ptr [ebp + C] ; Param 2

Using a stack frame allows parameters and local variables to be easily accessed. It also means that you can continue to push and then pop values on and off of the stack for whatever reason, without destroying the contents of your saved variables. Note that it is unwise to change the value of EBP, unless there is a valid reason for doing so. Changing its value without restoring it, will most likely result in a corruption of the stack, and the program will crash.

Stack Frames are used extensively in high level languages. In assembly language however, they are not used as frequently. In assembly language you can pass parameters through the registers. Since it is not always mecessary to pass parmeters by using the stack, there is not always a need for a stack frame. However, if your function requires local variables, a stack frame will be necessary, regardless of the method used for passing parameters. You should remember, though, since we are using assembly language, we want to utilize the registers as much as possible, so the use of stack variables should be minimized when possible.

Here follows a method of setting up and using a stack frame.

  ; MyProc: Takes 2 DWORD paramaters, passed on the stack
  ;         Uses 2 DWORD local variables
  MyProc proc
      push    ebp                     ; preserve EBP
      mov     ebp, esp                ; now set EBP to the current stack pointer
      sub     esp, 8                  ; adjust esp to point beyond 2 local variables
          
      mov     eax,dword ptr [ebp-08]  ; copy local variable 2 into eax
      mov     ebx,dword ptr [ebp+08]  ; copy parameter 1 into ebx
  
      add     esp,8                   ; adjust esp to boint before the two local 
                                      ; variables (point it to saved ebp)
      pop     ebp                     ; restore ebp        
      ret     8                       ; return from function call and
                                      ; adjust stack past the 2 parameters
  MyProc endp
This function manually sets up the stack frame, by pushing EBP and then adjusting the stack pointer ESP. It also manually cleans up the stack frame, by restoring EBP. This method, however can be greatly simplified by the use of 2 different instructions, ENTER and LEAVE. These instructions were designed to do exactly what we have just done manually. The ENTER and companion LEAVE instructions are provided to support block structured languages. The ENTER instruction (when used) is typically the first instruction in a procedure and is used to set up a new stack frame for a procedure. The LEAVE instruction is then used at the end of the procedure, right before the RET instruction, to release the stack frame.

ENTER <size>, <nesting level>
Creates a stack frame for a procedure. The size operand specifies the size of the stack frame. This is the number of bytes reserved on the stack to be used for the procedure's local variables. The nesting level operand specified the procedures nesting level, which determines the number of stack frame pointers copied from previous stack frames to the current one. For our purposes, the nesting level will always be 0.

LEAVE
Releases the stack frame set up by an earlier ENTER instruction. The LEAVE instruction copies the frame pointer (in the EBP register) into the stack pointer register (ESP), which releases the stack space allocated to the stack frame. The old frame pointer (the frame pointer for the calling procedure that was saved by the ENTER instruction) is then popped from the stack into the EBP register, restoring the calling procedure’s stack frame. A RET instruction is commonly executed following a LEAVE instruction to return program control to the calling procedure.

This example shows how to use ENTER and LEAVE to create a stack frame. This code functions identical to the previous example (MyProc), but is simpler, and runs faster. You can see that it greatly simplifies the code.

  ; MyProc2: Takes 2 DWORD paramaters, passed on the stack
  ;          Uses 2 DWORD local variables
  MyProc2 proc
      enter   8, 0                    ; set up stack frame
                                      ; set aside sapce for 2 local DWORDS
  
      mov     eax,dword ptr [ebp-08]  ; copy local variable 2 into eax
      mov     ebx,dword ptr [ebp+08]  ; copy parameter 1 into ebx
  
      leave                           ; clean up stack frame
      ret     8                       ; return from function call and
                                      ; adjust stack past the 2 parameters
  MyProc2 endp

Assembler Features

Assemblers such as MASM and TASM can furthur simplify parameter passing and local variable usage. By specify the calling convention in the .model directive, the assembler will take care of all of the details of creating the stack frame. This is accomplished by the including the following .model directive:

  .model flat, stdcall

This tells the assembler that every function uses the stdcall convention unless otherwise specified. The assembler will then insert the ENTER and LEAVE instructions to create the stack frames where appropriate.

You can also specify the calling convention on a function by function basis, as seen by the following code:

  MyFunction proc stdcall param1:DWORD, param2:DWORD
  ...
  MyFunction endp

Assemblers also allow for a much cleaner method of accessing local variables. Instead of referring to them as offsets of EBP, the assembler allows you to access them by name. The assembler will then translate this higher level syntax to the same low level instructions as we have seen, An example of how MASM or TASM simplifies locals follows:

  MyFunction proc stdcall param1:DWORD, param2:DWORD
      LOCAL var1:DWORD     ; alias to [ebp - 4]
      LOCAL var2:DWORD     ; alias to [ebp - 8]

      mov eax, [var1]      ; assembles to: mov eax, byte ptr [ebp - 8]  
      ret
  MyFunction endp

Conclusion

We have seen how to pass paramaters, access them, and use local stack variables. We have also learned how we can use the features of our assembler to make these concepts far easier to use.

Copyright (C) 1999 Bill Tyler
Email: BillAsm@usa.net
Sep 16, 1999