60

What is the structure of a stack frame and how is it used while calling functions in assembly?

peSHIr
  • 6,279
  • 1
  • 34
  • 46
bunty
  • 2,665
  • 8
  • 30
  • 27
  • 1
    http://stackoverflow.com/questions/2466170/drawing-a-stack-frame-for-x86-assembly/2466587#2466587 – Naveed Sep 13 '10 at 09:49

4 Answers4

179

Each routine uses a portion of the stack, and we call it a stack frame. Although an assembler programmer is not forced to follow the following style, it is highly recommended as good practice.

The stack frame for each routine is divided into three parts: function parameters, back-pointer to the previous stack frame, and local variables.

Part 1: Function Parameters

This part of a routine's stack frame is set up by the caller. Using the 'push' instruction, the caller pushes the parameters onto the stack. Different languages may push the parameters on in different orders. C, if I remember correctly, pushes them on right to left. That is, if you are calling ...

foo (a, b, c);

The caller will convert this to ...

push c
push b
push a
call foo

As each item is pushed onto the stack, the stack grows down. That is, the stack-pointer register is decremented by four (4) bytes (in 32-bit mode), and the item is copied to the memory location pointed to by the stack-pointer register. Note that the 'call' instruction will implicitly push the return address on the stack. Cleanup of the parameters will be addressed in Part 5.

Part 2: Stackframe back pointer

At this point in time, the 'call' instruction has been issued and we are now at the start of the called routine. If we want to access our parameters, we can access them like ...

[esp + 0]   - return address
[esp + 4]   - parameter 'a'
[esp + 8]   - parameter 'b'
[esp + 12]  - parameter 'c'

However, this can get clumsy after we carve out space for local variables and stuff. So, we use a stackbase-pointer register in addition to the stack-pointer register. However, we want the stackbase-pointer register to be set to our current frame, and not the previous function. Thus, we save the old one on the stack (which modifies the offsets of the parameters on the stack) and then copy the current stack-pointer register to the stackbase-pointer register.

push ebp        ; save previous stackbase-pointer register
mov  ebp, esp   ; ebp = esp

Sometimes you may see this done using only the 'ENTER' instruction.

Part 3: Carving space for local variables

Local variables get stored on the stack. Since the stack grows down, we subtract some # of bytes (enough to store our local variables):

sub esp, n_bytes ; n_bytes = number of bytes required for local variables

Part 4: Putting it all together. Parameters are accessed using the stackbase-pointer register ...

[ebp + 16]  - parameter 'c'
[ebp + 12]  - parameter 'b'
[ebp + 8]   - parameter 'a'
[ebp + 4]   - return address
[ebp + 0]   - saved stackbase-pointer register

Local variables are accessed using the stack-pointer register ...

[esp + (# - 4)] - top of local variables section
[esp + 0]       - bottom of local variables section

Part 5: Stackframe cleanup

When we leave the routine the stack frame must be cleaned up.

mov esp, ebp   ; undo the carving of space for the local variables
pop ebp        ; restore the previous stackbase-pointer register

Sometimes you may see the 'LEAVE' instruction replacing those two instructions.

Depending upon the language you were using you may see one of the two forms of the 'RET' instruction.

ret
ret <some #>

Whichever is chosen will depend upon the choice of language (or style you wish to follow if writing in assembler). The first case indicates that the caller is responsible for removing the parameters from the stack (with the foo(a,b,c) example it will do so via ... add esp, 12) and it is the way 'C' does it. The second case indicates that the return instruction will pop # words (or # bytes, I can't remember which) off the stack when it returns, thus removing the parameters from the stack. If I remember correctly, this is style used by Pascal.

starball
  • 20,030
  • 7
  • 43
  • 238
Sparky
  • 13,505
  • 4
  • 26
  • 27
  • 4
    +1 - Very Nice answer. Can you recommend some good books for these parts? – Abid Rahman K Feb 07 '13 at 14:13
  • As I rarely read programming books, I do not have any recommendations. The information above is just some stuff that I remember from school decades ago and stuff I've picked up from experimentation over the years. – Sparky Feb 07 '13 at 14:29
  • 1
    @AbidRahmanK: I recommend this course at Coursera https://www.coursera.org/course/hwswinterface It's a very nice course and should be taken seriously. – jyz May 13 '13 at 11:57
  • @Sparky its quite a explanation you gave and have upvote from my end but only doubt have is accessing the parameter "C" with [ebp+16] ,Calculation from my side says it should be [ebp+20],pushing old ebp on to stack will force esp to be decremented by four and after that ebp=esp and to access older ebp ,it should be [ebp+4] ,Please correct me if i am wrong here. – Amit Singh Tomar Jun 06 '13 at 09:58
  • @Sparky are you saying that the so-called 'stack frame' is actually more of a coding convention? No enforces on the machine part? – n611x007 Apr 10 '14 at 11:36
  • 1
    @naxa: A stack frame is a concept useful in logically separating variables stored on the stack. It is so useful, that there are relatively few cases where you would NOT want to use it. The x86 processor does not enforce its use, but it does strongly encourage it. For example: in 16-bit mode, the SP and BP registers were among the few registers that accept a displacement. In all modes, the CALL instruction automatically pushes the return address onto the stack while the RET instruction automatically pops the return address from the stack. I hope this helps. – Sparky Apr 10 '14 at 16:00
  • Great explanation man, this should be the accepted answer. Thanks! – Marcos Pereira May 29 '16 at 18:04
  • *However, this can get clumsy after we carve out space for local variables and stuff* this part does not justifies the quality of this answer. As soon as you think how things would be handled _without_ `ebp` (I had to draw this to understand), it's very easy to understand why that `ebp` is needed. very good explanation. – Eugene Apr 10 '19 at 19:45
  • For a long time I have been searching for a explanation like this. Thanks a lot – mjk6035 Aug 21 '19 at 04:55
  • What an extremely helpful and perfectly structured answer! Thank you very much! – dombg Sep 28 '20 at 11:22
18

The x86-32 stack frame is created by executing

function_start:
    push ebp
    mov ebp, esp

so it's accessible through ebp and looks like

ebp+00 (current_frame) : prev_frame
ebp+04                 : return_address
                         ....
prev_frame             : prev_prev_frame
prev_frame+04          : prev_return_address

There is some advantages of using ebp for stack frames by assembly instruction design, so arguments and locals usually are accessed using ebp register.

Abyx
  • 12,345
  • 5
  • 44
  • 76
  • This seems wrong to me - is not return address found at ebp+04 instead (see most upvoted answer)? – Suma Sep 05 '11 at 11:33
2

This is different depending on operating system and language used. Cause there are no general format for the stack in ASM, the only thing the stack is doing in ASM is to store the return address when doing a jump-subroutine. When executing a return-from-subroutine the address is picked up from the stack and put into the Program-Counter (memory location where next CPU execution instruction is to be reed from)

You will need to consult your documentation for the compiler you are using.

UnixShadow
  • 1,222
  • 8
  • 12
0

The x86 stack frame can be used by compilers (depending on the compiler) to pass parameters (or pointers to parameters) and return values. See this

Community
  • 1
  • 1
jacknad
  • 13,483
  • 40
  • 124
  • 194