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>
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 |
... | ...
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
The first one saved the current base pointer value onto the stack
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.
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 |
| | +------------+ +------------------------+ |
To summarize:
A function will call a child function
The process store the address of the next instuction of the current function onto the stack
The process go to the frst instruction of the child function ( at this moment a new stack frame will be create )
the process store the adress of the parent bottom stack frame onto the stack
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
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
The process will pop this value into EBP in order to restore the parent function stack frame
Now the process is back into the parent function stack frame and the top value is the saved EIP
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