content
3. Stack frame creation and destruction
No one in ancient times! Gubiao Lingyun is a friend, and the sword is my life.
1. Front
This chapter will look at how the memory of the function stack frame is used and recovered from the perspective of assembly. In order to reduce the cost of understanding assembly language, the effect brought by each step of the assembly instruction will be explained in a graphical way to gradually show the formation of the function stack frame. with the entire process of destruction.
Display environment: win10 && vs2019
2. Preliminary knowledge
The understanding of these preliminary knowledge has little to do with this article. The reason for preparing this knowledge is to make readers more confident that the formation and destruction process of function stack frames is like this.
Stack area : one of the four areas of memory. For use and management, the memory is divided into four parts. The stack area is one of the areas where the memory is divided. The usage of the stack is to use the high address part first, and then use the bottom address part. .
Function stack frame : that is, a piece of memory space opened up for a function when a function is called. Since this memory space is in the stack area, this space is called a function stack frame, or stack frame for short.
The top of the stack : As the name implies, it is the top of the stack, more precisely, it points to the top of the data stored in the stack area.
Bottom of the stack: The bottom of the stack.
Registers : registers are some small storage areas inside the cpu used to store data, which are used to temporarily store the data and operation results involved in the operation. Simply put, it is a device that is independent of memory and used to store small amounts of data.
ebp: stack bottom pointer register
esp: stack bottom pointer register
Other registers: ebx, esi, edi, ecx, eax
Push to the stack (push the stack) : first move the top pointer of the stack up by four bytes of space, and then put the data of the register into the four-byte space. The upward movement here refers to the movement at the lower address.
Push instruction : push a.
Illustration : Take push a as an example.
Pop out of the stack : Move the top pointer of the stack down by four bytes, where the down is to move the space of four bytes to the lower address. And put these four bytes of data into some register.
Pop instruction : pop a.
Illustration: Take pop a as an example.
Simple assembly operation instructions
mov ab : Assign b to a, the c language means a=b.
sub ab : Assign the result of ab to a, the expression in c language is a=ab.
add ab : Assign the result of a+b to a, the expression in c language is a=a+b.
Due to the cost of understanding, other assembly instructions encountered in this article will directly point out its effect.
3. Stack frame creation and destruction
Take the Add function call as an example
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int z = 0;
z = Add(a, b);
printf("%d\n", z);
return 0;
}
The assembly instructions corresponding to this code are as follows:
It should be noted that the main function is also called by other functions. The calling relationship is: __mainCRTStartup calls the main function, and the mainCRTStartup function calls __mainCRTStartup.
Before calling the main function again, the stack area is like this.
Instructions say:
int main() { 00F71E40 push ebp 00F71E41 mov ebp,esp 00F71E43 sub esp,0E4h
The above picture is for reference.
The first instruction : push the value of register ebp onto the stack
The second instruction : assign the value of register esp to ebp
The third instruction : assign esp-0E4h to the register esp. The image expression is that esp moves 4 bytes to the low address direction, the upper end is the low address, and the lower end is the high address, that is, it moves up 4 bytes of space.
The stack view becomes:
These three instructions simply open up a space for the main function in the stack area (the system will automatically open up this space for us.)
Instructions say:
00F71E49 push ebx 00F71E4A push esi 00F71E4B push edi
Push the values of the three registers onto the stack
The stack view becomes:
Instructions say:
00F71E4C lea edi,[ebp-24h] 00F71E4F mov ecx,9 00F71E54 mov eax,0CCCCCCCCh 00F71E59 rep stos dword ptr es:[edi]
We interpret these four instructions. The effect is to fill the stack frame space of the main function with the hexadecimal value cccccccc.
The stack view becomes:
Instructions say:
00F71E5B mov ecx,0F7C003h 00F71E60 call 00F7130C
These two instructions are used by the compiler to check, and beginners do not need to spend more time to understand the more detailed parts.
vs2013 doesn't have this check part, vs2019 check is strict.
Instructions say:
int a = 10; 00F71E65 mov dword ptr [ebp-8],0Ah int b = 20; 00F71E6C mov dword ptr [ebp-14h],14h int z = 0; 00F71E73 mov dword ptr [ebp-20h],0
The first assembly instruction: put 0Ah into the space of [ ebp-8 ], that is, put a into that space.
The second assembly instruction: put 14h into the space [ ebp-14h ], that is, put b into that space.
The third assembly instruction: put 0 into the space [ ebp-20h ]. That is, put z into that space.
Stack area icon:
Simply put: it is to put local variables into the corresponding function stack frame.
Instructions say:
z = Add(a, b); 00F71E7A mov eax,dword ptr [ebp-14h] 00F71E7D push eax 00F71E7E mov ecx,dword ptr [ebp-8] 00F71E81 push ecx
The first instruction : Put [ebp-20] 4 bytes of data in this space into eax. That is, put the data of b=20 into eax.
The second instruction : push the data of eax onto the stack.
The third instruction : put [ebp-8] 4 bytes of data in this space into ecx. That is, put the data of a=10 into ecx.
The fourth instruction : push the data of ecx onto the stack.
Stack area view:
The 20 and 10 here are the actual parameters we passed in the past, and the x and y called by the Add function later refer to these two spaces.
Then we can know: function parameters are passed from right to left. Here is the b passed first and then the a passed.
Instructions say:
00F71E82 call 00F710B4 调用的函数: int Add(int x, int y) { 00F71740 push ebp 00F71741 mov ebp,esp 00F71743 sub esp,0CCh 00F71749 push ebx 00F7174A push esi 00F7174B push edi 00F7174C lea edi,[ebp-0Ch] 00F7174F mov ecx,3 00F71754 mov eax,0CCCCCCCCh 00F71759 rep stos dword ptr es:[edi] 00F7175B mov ecx,0F7C003h 00F71760 call 00F7130C int z = x + y; 00F71765 mov eax,dword ptr [ebp+8] 00F71768 add eax,dword ptr [ebp+0Ch] 00F7176B mov dword ptr [ebp-8],eax return z; 00F7176E mov eax,dword ptr [ebp-8] } 00F71771 pop edi 00F71772 pop esi 00F71773 pop ebx 00F71774 add esp,0CCh 00F7177A cmp ebp,esp 00F7177C call 00F71235 00F71781 mov esp,ebp 00F71783 pop ebp 00F71784 ret
The first assembly instruction: call is a call instruction that calls the Add function.
After the last instruction, here I will directly introduce the effect.
00F71740 push ebp 00F71741 mov ebp,esp 00F71743 sub esp,0CCh
These three instructions open up the corresponding space size in the stack area for the Add function.
Stack area icon:
00F71749 push ebx 00F7174A push esi 00F7174B push edi
Push ebx, esi, edi onto the stack.
Icon:
00F7174C lea edi,[ebp-0Ch] 00F7174F mov ecx,3 00F71754 mov eax,0CCCCCCCCh 00F71759 rep stos dword ptr es:[edi]
Initialize the stack frame of the Add function and replace the data in it with cccccccc. (The exact value used to initialize the stack frame depends on the compiler)
00F7175B mov ecx,0F7C003h 00F71760 call 00F7130C
The checks made by the compiler are ignored.
int z = x + y; 00F71765 mov eax,dword ptr [ebp+8] 00F71768 add eax,dword ptr [ebp+0Ch] 00F7176B mov dword ptr [ebp-8],eax
Take the data in the [ebp+8] space and put it in eax
Take [ebp+0Ch] and add the data of eax and put it in eax.
Put the value of eax into ptr[ebp-8].
Icon:
return z; 00F7176E mov eax,dword ptr [ebp-8]
When returning, the return value is given to the register by means of the register.
00F71771 pop edi 00F71772 pop esi 00F71773 pop ebx 00F71774 add esp,0CCh 00F7177A cmp ebp,esp 00F7177C call 00F71235 00F71781 mov esp,ebp 00F71783 pop ebp 00F71784 ret
Code breakdown:
00F71771 pop edi 00F71772 pop esi 00F71773 pop ebx
Pop edi, esi, ebx from the stack
Icon :
00F71774 add esp,0CCh 00F7177A cmp ebp,esp 00F7177C call 00F71235 00F71781 mov esp,ebp 00F71783 pop ebp
0CCh is the size of the stack frame of the Add function
So esp moves down to the position of dbp.
After pop ebp, since the top of the stack points to the bottom of the stack frame of the main function, the pop ebp points to the bottom of the stack of the main function stack frame.
Icon:
After calling Add returns, continue to execute the following instructions.
00A717F7 add esp,8 00A717FA mov dword ptr [ebp-20h],eax return 0; 00A717FD xor eax,eax } 00A717FF pop edi 00A71800 pop esi 00A71801 pop ebx 00A71802 add esp,0E4h 00A71808 cmp ebp,esp 00A7180A call 00A71235 00A7180F mov esp,ebp 00A71811 pop ebp 00A71812 ret
The first instruction : move esp down by 8 bytes, that is, destroy the two consecutive formal parameters of x and y.
The second instruction : Give the return value of the Add function stored in the register eax to z.
Icon:
The next instruction is to recycle the stack frame of the main function. The recycling process is similar, so I will not explain it in detail.
4. Summary
The following function call is an example.
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int z = 0;
z = Add(a, b);
return 0;
}
Initial : The mainCRTStartup function calls __mainCRTStartup, and __mainCRTStartup calls the main function.
First allocate function stack frames for the above two functions on the stack area.
When calling the main function, allocate the function stack frame for the main function (the size is automatically opened up), and then fill the main function stack frame with the cccccccc value. (The specific value to initialize the function stack frame depends on the compiler).
When int a = 10 is executed, the value of the local variable a is put into a certain space of the main function stack frame, the same is true for int b = 20 and int z = 0, and their spaces are all in the function stack frame of the main function. .
When z=Add(a,b) is executed.
The parameters are passed first, and the order of parameter passing is from right to left. All first push b onto the stack, and then push a onto the stack.
These two spaces are y and, x. Note that y and x are not in the Add function stack frame, but in a separate space between the main function stack frame and the Add function stack frame.
Then open up the function stack frame for the Add function, and fill the Add function stack frame with the ccccccc value. (The specific value to initialize the function stack frame depends on the compiler).
When z=x+y is executed, a space is taken in the stack frame of the Add function as a local variable z, and the values of the y and x spaces are taken out and placed in z. (z is in the stack frame of the Add function).
When execution reaches return z, put the value of z into a register.
After that, the stack frame of the Add function is destroyed, the formal parameters x and y are destroyed, and the value of the register is given to z.
The same goes for destroying the main function afterwards.
The destruction here is not to set the stack frame data of the Add function to 0 or other numbers. The data in it is not directly lost, but directly tells the operating system that I don’t need this space anymore. The data in the stack frame of the Add function It still exists, but when you call a new function, the space of the Add function stack frame will be occupied by the new function and initialized to a value such as cccccccc, then the Add function stack frame space data will be lost.