C language function stack frame (detailed animation)

Table of contents

1. What is a stack frame

2. Related registers and assembly instructions

        1. Related registers

        2. Some assembly instructions

3. Program introduction

4. Process analysis (assembly angle)

        1. Execute the main function

        2. Form Add() function stack frame

        3. Execute the Add() function

        4. Add function stack frame release and return

5. Summary


1. What is a stack frame

        When a C program calls a function, it will pre-open a sufficient space for the function on the stack, and the contents of the subsequent functions, such as non-static local variables and return values, will be stored in this space. This space is called a stack frame .

        When a function is called, a stack frame is formed; when the function returns, the stack frame is also released. The so-called release refers to setting a certain section of space as invalid so that it can be overwritten instead of being emptied.

2. Related registers and assembly instructions

        In this issue, we will use the perspective of assembly to understand the formation and release process of the stack frame before and after the function call. First, let us first understand the following related registers and possible assembly commands:

        1. Related registers

register name Function
eax General-purpose registers, which hold temporary data and are often used for return values
ebx General purpose registers, hold temporary data
ebp bottom register
esp top-of-stack register
eip Instruction register, which holds the address of the next instruction of the current instruction

        2. Some assembly instructions

mnemonic illustrate
mov data transfer instruction
push The data is pushed onto the stack, and the top register of the esp stack also changes
pop The data is popped to the specified location, and the top register of the esp stack is also changed
sub Subtraction instruction
add addition instruction
call Function call, 1. Push the return address 2. Transfer to the target function
jump By modifying eip, transfer to the target function and call
ret Restore the return address, push into eip, similar to the pop eip command

3. Program introduction

        The following is the sample code studied in this article, which is compiled under vs2022 (different compilers may have slightly different effects):

#include<stdio.h>


int Add(int x, int y)    //求出两数和并返回
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 0xA;       //定义两个变量,用16进制
	int b = 0xB;
	int c = 0;
	c = Add(a, b);     //求和函数
	printf("%d", c);   //打印结果
	return 0;
}

 We will demonstrate the Add () function in four stages: executing the content of the main function, forming the stack frame of the Add function, executing the content of the Add function, and releasing the stack frame of the Add function.

4. Process analysis (assembly angle)

        1. Execute the main function

        We use VS, enter debugging, open the memory window, register window, and go to the disassembly as follows:

        

        What we need to know here is that the main function is also a function, it is called by the _tmainCRTStartup function , and _tmainCRTStartup is called by the mainCRTStartup function , and the mainCRTStartup function is called by the operating system . Therefore, the main function will also form a stack frame. The assembly code before defining a is used to form the main function stack frame and initialize it. It is the same as the stack frame formation process of the Add function we will discuss later , so it will not be discussed here. We start the analysis directly after int a=0xA:

         We F10 run to the assembly instruction corresponding to int a=0xA, at this time eip points to the address of the next instruction to be pointed to, which is the instruction corresponding to int a:

         According to ESP, EBP, we get the position of the stack frame of the main function on the stack as follows ( the address increases from top to bottom, the same below ):

        Click F10 to execute the instruction. Since the stack grows from high address to low address , a space is opened up 8 bytes from the bottom of the stack and the data is moved into it. At this time, the bottom register of the stack remains unchanged, and the memory data at ebp-8 is changed to 0AH. as follows:

         At the same time, eip automatically points to the next instruction to be executed. In the same way as a, variables b and c are pushed onto the stack as follows:

        The specific process animation is as follows: 

 

We can find that the variables are not continuous 

         The next step is to execute the next statement, call the function and assign a value to c. There are 7 instructions in total:

         The first is the 4 instructions before calling Add() (that is, before the call instruction ). We can see that the function of the first two instructions is to first move the value of the variable b to the eax register, and then push it into the stack by pushing the stack . , at this time the top of the stack moves up by a b variable size:

         The same is true for the last two instructions, copy the value of variable a into the ecx register, and then push it onto the stack:

        The specific process animation is as follows:

 

The formation of these two temporary variables is what we call parameter instantiation . We found that the formal parameter instantiation is formed before the function is officially called (here before the call), and the order of formal parameter instantiation is from right to left according to the parameter list . At the same time, we can find that the variable space formed by pushing the stack is continuous.

         Next, we will execute the function call instruction. Since we modify the eip register to transfer to the target function address through the jump instruction, after the Add function call is completed, we need to return to the main function to execute the follow-up content, so we need to set the address of the next instruction first Save it up and make the jump. Therefore, this instruction is divided into two steps: 1. Push the return address onto the stack 2. Transfer to the target function.

 

        Click F11 to enter the function, we can find that the instruction address after the function returns is pushed into the stack (ie 00EE18F7), then modify eip to jump, and transfer to the Add() function:

        The specific process animation of pushing the stack is as follows:

  

 So far, we have entered the Add() function, and then we will start preparing to form the stack frame of the Add() function.


        2. Form Add() function stack frame

        The first three instructions are the formation process of the stack frame , and the next few are the initialization and assignment of the stack frame. Let's focus on analyzing the first three instructions:

         The first is the first instruction. Click F10 to push the contents of the register at the bottom of the stack onto the stack, that is, push the address at the bottom of the stack of the main function onto the stack:

         Since the stack is pushed , the top of the stack is offset by 4 bytes to the low byte, and the address of the bottom of the main function stack is saved. The specific animation is as follows:

         Then there is the second instruction, click F10, move the contents of the top register to the bottom register, so that the top register and the bottom register point to the same address space:

        Finally, the third instruction, click F10, subtract 0CCH from the contents of the esp stack top register, and make it offset 0CC bytes to the lower address, as follows:

        The specific process animation diagram of the second instruction and the third instruction is as follows:

 

 From this we get a space with a size of 0CC bytes , which is the stack frame of the Add() function . eap points to the top of the stack frame, and ebp points to the bottom of the stack frame.


 

        3. Execute the Add() function

        We skip the initialization stack frame part and run to the instruction corresponding to int z=0:

         Click F10, which is the same as the previous a and b variables. This instruction is to allocate space for z through mov , and put 0 into the bottom 8 bytes of the stack:

        The specific process animation is as follows:

 

        Click F10, execute z=x+y, this statement has three instructions, first move the contents of ebp shifted down by 8 bytes to the eax register, and then shift ebp down by 0CH (12) words The content of the section is added to the content in the eax register and stored in the eax register, and finally the content of the eax register is stored in the ebp upward offset of 8 bytes:

         It is not difficult to find that the address space obtained by pushing the stack is continuous , and the several spaces below the bottom of our stack are all obtained by pushing the stack . Therefore, ebp+8 is the address of the temporary variable x , and ebp+0CH is the address of the temporary variable y . Thus, we add the values ​​of x and y and finally store them in ebp-8, which is the variable z .

         The specific process animation is as follows:

        Finally, execute the return z statement to put the content at ebp-8 (ie z) into the eax register :

 

        The specific process animation is as follows: 

 

 So far, the ADD function call is completed, enter the last step, and the stack frame is released (destroyed).

        4. Add function stack frame release and return

        For the destruction of the stack frame, let's focus on the last three statements. The first few statements correspond to the initialization operation when the previous stack frame was created, and we will not go into details.

         The first is the first mov command, we click F10 to run, the value of the ebp stack bottom register is assigned to the esp stack top register, at this time ebp and esp point to the same address space

        The specific animation is as follows:

In fact, at this time, the space where the stack frame of the Add function is located is invalidated, and the stack frame is released. 

         The next step is to restore the stack frame of the main function.

        We click F10 to execute the next pop instruction, pop the contents of the top of the stack and put it into the bottom register of the ebp stack, and at the same time, the pointer of the top register of the esp stack changes. Since the content at the top of the stack is the bottom address of the main function stack , ebp will point to the bottom of the main stack frame after the pop operation is completed.

         The specific process animation is as follows:

         Finally, the ret instruction is executed. The function of ret is to restore the return address and push into eip, which is similar to the pop eip instruction , that is, pop the top element of the stack ( the saved instruction return address ) into the eip instruction register to change the next executed instruction. We click F10 and find that we have returned to the main function. At this time, the content of eip is the address of the next main function instruction we saved before, and the top register of the esp stack changes:

         The specific process animation is as follows:

         Then execute the next add instruction in the main function, add 8 to the value of the top register of the esp stack and save it back to the top register of the esp stack. At this time, the esp is shifted downward by 8 bytes, pointing to the top of the stack of the original main function:

         The specific process animation is as follows:

At this point, the stack frame of the main function is restored, the stack frame of the Add function is released, the calling process of the Add function ends, and the subsequent content of the main function is entered.

        Finally, execute the mov statement to assign the return value of the Add function stored in the eax register to the variable c:

          The specific process animation is as follows:

The above is the whole process of creating and releasing (destroying) the Add() function stack frame. Subsequent printf() is also a function, and the Add() function will also create a function stack frame, but the overall steps are the same, so I won't explain it here. 


5. Summary

Through the above analysis, we can draw several conclusions :

1. Before the function is officially called (call), the formal parameters will be instantiated to allocate storage space. The order of formal parameter instantiation is from right to left.

2. The temporary space is created by using the mov command inside the corresponding function stack frame .

3. After the function call is completed, the stack frame structure is released.

4. The temporary nature of temporary variables is: the stack frame is temporary.

5. There is a cost to mobilizing functions, which is reflected in time and space. The essence is that there is a cost to forming and releasing stack frames.

6. Function calls, temporary variables formed by copying, and the positional relationship between variables and variables are regular.

7. The stack frame of the function is formed by itself, and how much the esp is reduced is determined by the compiler. That is, the size of the stack frame is determined by the compiler. The compiler has the ability to know the size of all types corresponding to the defined variables.


 The above is the whole content of this issue.

It's not easy to make, can you give it a thumbs up before leaving? qwq

Guess you like

Origin blog.csdn.net/m0_69909682/article/details/128663886