6.9. Stack Frames

When high level code from a language like C++ is compiled, memory required for local variables needs to be managed as part of the stack. To do this, each function reserves a chunk of memory sufficient to back up necessary registers as well as to store local variables. This chunk of memory is called a stack frame.

To keep track of where data is within the stack frame, we need a fixed reference point. The stack pointer can be tricky to use for this as it may change during the function if more memory is allocated. So in ARM, functions set up a frame pointer - a register that holds the address where the function’s stack frame begins. By convention, this register is r11 - fp is another name for r11.

Note

Some times compilers will omit creating a frame pointer and just address relative to the stack pointer. We are going to focus on the full version with a frame pointer.

In the same way that nested function calls need to worry about backing up the link register, each function is going to want its own value in the fp. Unlike the lr, which each function is responsible for backing up on its own, the fp is assumed to not be modified by other functions. So when a function does want to place a new value in fp, it is responsible fo backing up the fp of the previous function and restoring it before returning.

As we start a function, we push the current lr (this function’s return address) and fp (the caller’s frame pointer) values so we can restore them at the end of the function. We then set the fp to hold the address where the start of the stack frame is. Finally, we allocate space for any variables in the function by subtracting from the stack pointer enough bytes to reserve the space required.

Here is what the process looks like:

AddressContents
0xffffffec
0xfffffff0
0xfffffff4
0xfffffff8
0xfffffffc
0x00000000... sp
AddressContents
0xffffffec
0xfffffff0
0xfffffff4
0xfffffff8old fp sp
0xfffffffclr
0x00000000...
AddressContents
0xffffffec
0xfffffff0
0xfffffff4
0xfffffff8old fp sp
0xfffffffclr fp
0x00000000...

The frame pointer is set to sp + 4 to make it hold the address of the start of the stack frame.

AddressContents
0xffffffecmore??? sp
0xfffffff0second variable
0xfffffff4first variable
0xfffffff8old fp
0xfffffffclr fp
0x00000000...

Space is allocated for all needed variables.

AddressContents
0xffffffecmore??? sp
0xfffffff0second variable (fp - 12)
0xfffffff4first variable (fp - 8)
0xfffffff8old fp (fp - 4)
0xfffffffclr fp
0x00000000...

Items can be found relative to the address stored in fp. Assuming all the variables are a machine word in size (4 bytes), the first variable is always at fp - 8, the second at fp - 12, etc…

To tear down the stack, we reverse the process. We add to the stack pointer to deallocate the space reserved for variables, then pop the lr and fp to restore the values we started with:

AddressContents
0xffffffecmore??? sp
0xfffffff0second variable (fp - 12)
0xfffffff4first variable (fp - 8)
0xfffffff8old fp (fp - 4)
0xfffffffclr fp
0x00000000...
AddressContents
0xffffffecmore???
0xfffffff0second variable
0xfffffff4first variable
0xfffffff8old fp sp
0xfffffffclr fp
0x00000000...

The stack pointer is moved to deallocate the space for variables.

AddressContents
0xffffffecmore???
0xfffffff0second variable
0xfffffff4first variable
0xfffffff8old fp
0xfffffffclr
0x00000000... sp

The lr and fp are popped, restoring their old values.

When one function calls another, each new function’s stack frame is built on top of the last. As a function exits and its stack frame is removed, the stack pointer and frame pointer are used to restore the previous stack frame:

AddressContents
0xffffffe0
0xffffffe4
0xffffffe8
0xffffffec
0xfffffff0second variable for foo sp
0xfffffff4first variable for foo
0xfffffff8old fp (for foo's caller)
0xfffffffclr for foo fp
0x00000000...

The foo function’s stack frame occupies 0xfffffff0-0xfffffffc

AddressContents
0xffffffe0more??? sp
0xffffffe4first variable for bar
0xffffffe80xfffffff8 (fp for foo)
0xffffffeclr for bar fp
0xfffffff0second variable for foo
0xfffffff4first variable for foo
0xfffffff8old fp (for foo's caller)
0xfffffffclr for foo
0x00000000...

Bar’s stack frame is built on top of foo’s. It occupies 0xffffffe0-0xffffffec. It stores foo’s fp - 0xfffffff8 - into its stack frame so that it can be restored.

AddressContents
0xffffffe0more???
0xffffffe4first variable for bar
0xffffffe80xfffffff8 (fp for foo)
0xffffffeclr for bar
0xfffffff0second variable for foo sp
0xfffffff4first variable for foo
0xfffffff8old fp (for foo's caller)
0xfffffffclr for foo fp
0x00000000...

When bar’s stack frame is torn down, sp ends up at the end of foo’s stack frame and 0xfffffff8 is popped back into fp, which restores the frame pointer for foo.

Note that fp and sp both end up in the same position they were before bar was called.

You have attempted of activities on this page