Stack frame

A stack frame is a portion of memory that is used by a program to store local variables and information about the current state of a subroutine. In most programming languages, a stack frame is created each time a subroutine is called, and is destroyed when it returns.

#include <stdio.h>

void HelloWorld(void){
  printf("World !\n");
}

int main(void) {
  printf("Hello ");
  HelloWorld();
  return 0;
}

In this example, when the process enter into the HelloWorld function, there is two stack frame into the stack, one for the main function and another for the HelloWorld function.

During the execution, the "Base pointer" register (for example EBP in x86 architecture) point the bottom of the current stack frame and the "stack pointer" register (for example ESP in x86 architecture) point to the top of the stack which is also the top of the current stack frame.

                     |----------------------------------|
                     |               ^                  |
                     |               |                  |
                     | ...                              |
   stack pointer --> | +------------------------------+ | 
                     | |                              | |
                     | |     HelloWord stack frame    | |
                     | |                              | |
   Base Pointer  --> | +------------------------------+ | 
                     | +------------------------------+ | 
                     | |                              | |
                     | |       main stack frame       | |
                     | |                              | |
                     | +------------------------------+ |
                     |  ...                             |
                     |----------------------------------|

Prologue

To be able to properly understand the rest of this article, some basic knowledge of assembly language is helpful.

The prologue of a function is the code that is executed at the beginning of the function, before any other function's code.

Code example

#include <stdio.h>

int Addition(int a, int b){
  return a+b;
}

int main(void) {
  int c = 0;
  c = Addition(1, 2);
  printf("%d + %d = %d\n",1,2,c);
  return 0;
}

Here is the assembly code of the main function :

0x565561b0 <+0>:	lea    ecx,[esp+0x4]
0x565561b4 <+4>:	and    esp,0xfffffff0
0x565561b7 <+7>:	push   DWORD PTR [ecx-0x4]
0x565561ba <+10>:	push   ebp
0x565561bb <+11>:	mov    ebp,esp
0x565561bd <+13>:	push   ebx
0x565561be <+14>:	push   ecx
0x565561bf <+15>:	sub    esp,0x10
0x565561c2 <+18>:	call   0x565560a0 <__x86.get_pc_thunk.bx>
0x565561c7 <+23>:	add    ebx,0x2e39
0x565561cd <+29>:	mov    DWORD PTR [ebp-0xc],0x0
0x565561d4 <+36>:	push   0x2
0x565561d6 <+38>:	push   0x1
0x565561d8 <+40>:	call   0x56556199 <Addition>
0x565561dd <+45>:	add    esp,0x8
0x565561e0 <+48>:	mov    DWORD PTR [ebp-0xc],eax
0x565561e3 <+51>:	push   DWORD PTR [ebp-0xc]
0x565561e6 <+54>:	push   0x2
0x565561e8 <+56>:	push   0x1
0x565561ea <+58>:	lea    eax,[ebx-0x1ff8]
0x565561f0 <+64>:	push   eax
0x565561f1 <+65>:	call   0x56556030 <printf@plt>
0x565561f6 <+70>:	add    esp,0x10
0x565561f9 <+73>:	mov    eax,0x0
0x565561fe <+78>:	lea    esp,[ebp-0x8]
0x56556201 <+81>:	pop    ecx
0x56556202 <+82>:	pop    ebx
0x56556203 <+83>:	pop    ebp
0x56556204 <+84>:	lea    esp,[ecx-0x4]
0x56556207 <+87>:	ret 

Into the main function, when the Addition function need to be executed, the call instruction is executed, and in fact the process execute the two followed instruction :

push eip
jmp  <function_address> // 0x56556199 here

At this moment, the instruction pointer is saved onto the stack

Before that, the process will store the function parameters onto the stack.

  address   |   values
------------+-------------------------------------------------------------------
            |   +-------------------- main stack frame ----------------------+
            |   | +-saved eip -+ +---- function params ---+                  |
0xffffd274: |   | | 0x565561dd | |0x00000001	0x00000002|	0x00000001   |
            |   | +------------+ +------------------------+                  |
0xffffd284: |   |   0xffffd354	  0xffffd35c	0x00000000	0xffffd2b0   |
0xffffd294: |   |   0xffffd354	  0xffffd35c	0x00000000	0xffffd2b0   |
  ...       |    ...

Note that the saved Instruction Pointer value point to *main+50 that directly follow the call of the Addition Function

At the jmp instruction, the Intruction Pointer will now point at the beggining of the Addition function (0x565561dd)

Here is the assembly code of the Addition function :

0x56556199 <+0>:	push   ebp
0x5655619a <+1>:	mov    ebp,esp
0x5655619c <+3>:	call   0x56556208 <__x86.get_pc_thunk.ax>
0x565561a1 <+8>:	add    eax,0x2e5f
0x565561a6 <+13>:	mov    edx,DWORD PTR [ebp+0x8]
0x565561a9 <+16>:	mov    eax,DWORD PTR [ebp+0xc]
0x565561ac <+19>:	add    eax,edx
0x565561ae <+21>:	pop    ebp
0x565561af <+22>:	ret

At the begining of the fonction, there is two instructions. This is the prologue :

0x56556199 <+0>:	push   ebp
0x5655619a <+1>:	mov    ebp,esp
  1. The first one saved the current base pointer value onto the stack

  2. The second set the base pointer value as the stack pointer value. At this moment base pointer and stack pointer have the same value, this is the begining of the stack frame.

Note, if the process have to push a local variable into the stack, the stack pointer will take some marge after setting the ebp register.

0x0804926a <+0>:	push   ebp
0x0804926b <+1>:	mov    ebp,esp
0x0804926d <+3>:	sub    esp,0xc

Here is the stack state after the execution of theses instructions

   address     |   values
---------------+------------------------------------------------------------------
               |   +---------------- HelloWorld stack frame -----------------+
               |   | +------------ stack marge -------------+ +-saved ebp -+ |
   0xffffd264  |   | | 0x00000000   0x00000000   0x00000000 | | 0xffffd298 | | 
               |   | +--------------------------------------+ +------------+ |
               |   +---------------------------------------------------------+
               |   +-------------------- main stack frame -------------------+
               |   | +-saved eip -+ +---- function params ---+               |
   0xffffd274: |   | | 0x565561dd | | 0x00000001  0x00000002 |	0x00000001   |
               |   | +------------+ +------------------------+               |

Now Base Pointer poit to 0xffffd270 and Stack Pointer will point "0xffffd264"

To summarize:

  1. A function will call a child function

  2. The process store the address of the next instuction of the current function onto the stack

  3. The process go to the frst instruction of the child function ( at this moment a new stack frame will be create )

  4. the process store the adress of the parent bottom stack frame onto the stack

  5. the process set the "Base Pointer" registry at the same value as the "Stack Pointer" registry ( the stack frame is now created )

Epilogue

At the end of a function there is some instruction to reset the Base Pointer at the parent bottom stack frame address value and to reset the instruction pointer at the next instruction of the parent function.

0x565561ae <+21>:	pop    ebp
0x565561af <+22>:	ret
  1. At the end of the execution of a function, the last value into the stack frame is the base address of the parent function stack frame

  2. The process will pop this value into EBP in order to restore the parent function stack frame

  3. Now the process is back into the parent function stack frame and the top value is the saved EIP

  4. The process will pop this value into EIP in order to restore the instruction pointer and then continue the execution flow into the parent function

Resume

The program is read instruction by instruction. During a call , the current instruction Pointer (EIP) is pushed onto the stack, and then control is transferred to the address given by the call. Here, a new stack frame is set onto the stack and the instructions of the function are executed one after the other until it will finish and then trash this stack frame and return to the parent function with the RET instruction that retrieves the previously recorded EIP value, in order to return to where the program was, without losing the thread!

Last updated