C function call stack (1)

Source: Internet
Author: User
The execution of a program can be thought of as a sequence of function calls. When a function finishes executing, the program returns to the next instruction (immediately after the call instruction) that invoked it. Function call procedures are usually implemented using a stack, with each user-mode process corresponding to a call stack. The compiler USES the stack to pass function arguments, save the return address, temporarily store the register's original value (the context of the function call) for recovery, and store local variables.

The stack layout and function call methods may differ between processors and compilers, but the basic concept of the stack is the same.





1 register allocation
Register is an important carrier for processor to process data or run program, which is used to store data and instructions used in program execution. So the implementation of the function call stack is closely related to the processor register group.

The Intel 32-bit architecture (IA32) processor contains eight four-byte registers, as shown in the figure below:


In the original 8086, the registers were 16 bits, each with a special purpose, and the register city reflected its different USES. Due to the flat addressing mode of the IA32 platform, the need for special registers was greatly reduced, but the register names were retained for historical reasons. In most cases, the first six registers shown above can be used as general-purpose registers. Some instructions may register at regular as the source or purpose registers, as some of the special arithmetic operation instructions imull mull/CLTD idivl/divl requires a parameter must be in % eax, its computational results in % edx (who 32 - bit) and % eax (lower32 - bit); The return value of a function is usually stored in %eax, and so on. To avoid compatibility problems, the ABI specification defines the specific role of this set of general purpose registers (shown in figure).

Registers %eax, %ebx, %ecx, and %edx can each be used as two separate 16-bit registers, while low 16-bit registers can continue to be used as two separate 8-bit registers. The compiler selects the appropriate register based on the operand size to generate the assembly code. At the assembly language level, this set of universal registers is referred to as %e(AT&T syntax) or directly beginning with e(Intel syntax), such as mov $5, %eax, or mov eax, where 5 means that the number 5 is immediately assigned to register %eax.

In x86 processors, EIP is an Instruction register that points to the address of the Instruction to be executed next to the processor (the offset within the snippet of code), and the value of EIP increases each time the corresponding assembly Instruction is executed. ESP(Stack Pointer) is a Stack Pointer register that holds the Stack top address (also the top of the system Stack) of the Stack frame corresponding to the execution function and always points to the Stack top. EBP(Base Pointer) is the stack frame Base address Pointer register, which holds the bottom address of the stack frame corresponding to the execution function and is used by the C runtime to access local variables and parameters in the stack.

Note that EIP is a special register and cannot be accessed in the same way as a universal register, that is, no OpCode can be found to address EIP and read or write to it. EIP can be changed implicitly by instructions such as JMP, call, and ret (in fact, it changes all the time).

For cpus of different architectures, register names are prefixed differently to indicate register sizes. For example, the x86 architecture prefixes the name with the letter "e(extended)" to indicate register sizes of 32 bits; The x86_64 schema prefixes the name with the letter "r", indicating that the register sizes are 64 bits.

When compiling a C program into an assembler, the compiler should follow the register function definitions specified in the ABI. Likewise, you should follow when writing an assembler, or you may not be able to write an assembler that works with a C program.

Stack frame pointer register

In order to access function local variables, you must be able to locate each variable. The position of the local variable relative to the stack pointer ESP is determined as it enters the function. In theory, the variable can be referred to by ESP plus an offset, but ESP varies with the push and pull of the variable during the execution of the function. Although in some cases the compiler can track variable operations in the stack to correct offsets, considerable administrative overhead is introduced. And on some machines (such as Intel processors), using ESP plus offsets to access a variable requires multiple instructions.

Therefore, many compilers use the Frame Pointer register FP(Frame Pointer) to record the stack Frame base address. Both local variables and function parameters can be referenced by frame Pointers because their distance to FP is not affected by push and push operations. Some data refer to a frame pointer as a lb-local base pointer.

In Intel cpus, the register BP(EBP) is used as a frame pointer. In Motorola cpus, any address register other than A7(stack pointer SP) can be used as FP. As the stack grows downward (low address), the offset of the function parameter is positive and the offset of the local variable is negative, based on the FP address.

Register 2 use convention
The program register group is the only resource that can be Shared by all functions. Although only one function is executing at any one time, make sure that the called function does not modify or overwrite the register values that the main function will use later when it calls other functions. Therefore, IA32 has a uniform set of register usage conventions that all function calls, including library functions, must follow.

By convention, % eax and % edx and % ecx register callback function to save registers (caller - saved registers), when a function call, if want to maintain the register values advocate tone function, you must explicitly save it before calling in the stack; The called function can override these registers without destroying the data required by the main call function. Register % ebx, % % esi and edi is preserved by callback function register (the callee - saved registers), which is covered by callback function in the register values, must first to press the original value of the register into stored in the stack, and restore its original value from the stack before the function returns, because may also use these registers in a calling routine. In addition, the called function must hold registers %ebp and %esp and restore them to their pre-call values after the function returns, i.e., the stack frame of the main call must be restored.

Of course, this is done behind the scenes by the compiler. However, be careful to follow these conventions when writing assembler programs.





3 stack frame structure
Function calls are often nested, with information about multiple functions on the stack at the same time. Each unfinished function occupies a separate contiguous area, called a Stack Frame. A stack frame is a logical fragment of a stack that is pushed onto the stack when a function is called and ejected from the stack when the function returns. Stack frames store function parameters, local variables, and data needed to restore the previous stack frame.

The compiler USES stack frames to make the allocation and release of function parameters and local variables transparent to the programmer. Before the compiler hands over control to the function itself, specific code is inserted to push the function parameters into the frame and enough memory is allocated to hold the local variables in the function. One benefit of using a stack frame is that recursion is made possible, because each recursive call to a function is assigned a new stack frame to that function, thus subtly isolating the current call from the last.

The stack frame boundaries are defined by the stack frame base address pointer EBP and the stack pointer ESP (the pointer is stored in the corresponding register). EBP points to the bottom of the current stack frame (high address) and is fixed in the current stack frame; ESP points to the top of the current stack frame (low address) and moves as data is pushed and pushed when the program is executed. So access to most of the data in the function is based on the EBP.

To be more descriptive, the EBP is referred to as the frame base pointer and the ESP as the stack top pointer, and is denoted as % EBP and % ESP when referring to the assembly code.



The typical memory layout of the function call stack is shown below:


The stack frame layout of caller and callee is shown in the figure. "m(%ebp)" represents the memory space with ebp as the base address and offset of m bytes. The figure is based on two assumptions: first, the return value of the function is not a structure or a consortium, otherwise the first parameter will be at "12(%ebp)"; Second, each parameter is a 4-byte size (stack granularity is 4 bytes). In the following sections of this paper, the parameter transfer and size will be further discussed. In addition, functions can have no arguments or Local variables, so "Argument" and "Local Variable" in the diagram are not necessary parts of the function stack frame structure.

As can be seen from the figure, the push order when the function is called is

Arguments N~1→ host function return address → host function frame base pointer EBP→ local variables of the called function 1~N

Where, the calling function pushes the parameters in sequence according to the calling convention (from right to left in the figure), and then pushes the instruction pointer EIP to hold the return address of the calling function (the address of the next instruction to be executed). Upon entering the called function, the called function pushes the frame base pointer EBP of the main function and assigns the pointer ESP value of the top of the stack of the main function to the EBP of the called function (as the bottom of the stack of the called function), then changes the ESP value to make room for the local variables of the function. At this point, the frame base pointer of the called function points to the bottom of the stack of the called function. Based on this address, the return address and parameter value of the calling function can be obtained up (bottom of the stack), and the local variable value of the calling function can be obtained down (top of the stack), and the frame base pointer value of the calling function of the previous layer is stored at this address. After the call is completed, the EBP pointer value is assigned to ESP, which again points to the bottom of the called function stack to release the local variable. Then pop the pressed main function frame base pointer to EBP and pop the return address to EIP. The ESP continues to move up and over the parameters, eventually returning to the state before the function was called, restoring the stack frame of the original calling function. This recursion results in a function call stack.

The EBP pointer remains unchanged during the current function run (when no other function is called). Before the function call, the ESP pointer points to the address at the top of the stack as well as the address at the bottom of the stack. After the function completes initialization such as field protection, the ESP always points to the top of the current function stack frame, at which point the EBP is treated as an old EBP stack if the current function calls another function, and the content of the new function is pushed from the current ESP point.

If the called function save register needs to be saved in the function (e.g. ESI, EDI), the compiler saves the EBP value when it is saved, or delays saving until the local variable space is allocated. The stack frame does not specify a standard storage location for the called function's register space. A function call stack layout containing registers and temporary variables might look like the following:

In a multithreaded (task) environment, the storage area that the stack top pointer points to is the stack currently in use. An important job of switching threads is to set the stack top pointer to the stack top address of the current thread.

The following code is used for the function stack layout example:


/ / StackFrame. C

# include < stdio, h >

# include < string. H >


Struct Strt {

Int is;

Int member2;

Int member3;

};


# define PRINT_ADDR (x) printf (" & "# x" = % p \ n ", & x)

Int StackFrameContent(int para1, int para2, int para3){

Int locVar1 = 1;

Int locVar2 = 2;

Int locVar3 = 3;

Int arr [] = {0 x11, x22 0, 0 x33};

Struct Strt tStrt = {0};

PRINT_ADDR (para1); // if para1 is char or short, then print the address of the integer temporary variable on the stack corresponding to para1!

PRINT_ADDR (para2);

PRINT_ADDR (para3);

PRINT_ADDR (locVar1);

PRINT_ADDR (locVar2);

PRINT_ADDR (locVar3);

PRINT_ADDR (arr);

PRINT_ADDR (arr [0]);

PRINT_ADDR (arr [1]);

PRINT_ADDR (arr [2]);

PRINT_ADDR (tStrt);

PRINT_ADDR (tStrt. Are;

PRINT_ADDR (tStrt member2);

PRINT_ADDR (tStrt member3);

Return 0;

}


Int main (void) {

Int locMain1 = 1, locMain2 = 2, locMain3 = 3;

PRINT_ADDR (locMain1);

PRINT_ADDR (locMain2);

PRINT_ADDR (locMain3);

StackFrameContent (locMain1 locMain2, locMain3);

Printf ("[locMain1,2,3] = [%d, %d, %d]\n", locMain1, locMain2, locMain3);

Memset (& locMain2, 0, 2 * sizeof (int));

Printf ("[locMain1,2,3] = [%d, %d, %d]\n", locMain1, locMain2, locMain3);

Return 0;

}


StackFrame




After compiling and executing the link, the output prints as follows:



Figure 4 StackFrame output

The function stack layout example is shown below. For intuition, other addresses lower than the starting high address 0xbfc75a58 are denoted by dots, such as 0x.54 for 0xbfc75a54, and so on.



Figure 5 StackFrame StackFrame

The memory address decreases from bottom to top of the stack, which is the process of gradually moving the ESP pointer to the lower address. And the member variable in the structure tStrt memberX address =tStrt first address +(memberX offset), that is, the closer the member variable is to the first address of tStrt, the smaller its memory address is. Therefore, the order in which struct member variables are pushed is the opposite of the order in which they are declared in the struct.

When a function call is passed by value, the arguments (locMain1~3) passed in are stored at different addresses than the arguments (para1~3) that are operated on within the called function, so the called function cannot directly modify the arguments of the main call (the operation on the parameters is equivalent to modifying a copy of the arguments). For modification purposes, you need to pass a pointer to the real reference (that is, the address of the variable) to the called function.

In addition, "[locMain1,2,3] = [0, 0, 3]" is because when the memset function is called on the four-byte parameter locMain2, it will clear 8 bytes continuously from the low address to the high address, thus clearing locMain1 at the high address by mistake.

Note that the layout of local variables depends on factors such as compiler implementation. So, when you remove the print statement from the StackFrameContent function, the variables locVar3, locVar2, and locVar1 might be stored in order from high to low! Also, local variables are not always on the stack and are sometimes stored in registers for performance (speed) reasons. Local variables of array/structure types are usually allocated in stack memory.

Function local variable layout

Unlike the function call convention, which specifies how parameters are passed in, the layout of local variables is not specified. The compiler calculates the total amount of space required for the function's local variables and determines whether they are stored in registers or allocated (or even optimized) on the program stack -- some processors do not have a stack. The spatial distribution of local variables has nothing to do with the main call function and the called function. The distribution of local variables of the function cannot be determined only from the source code of the function.

Based on different compiler versions (local variables in gcc3.4 are pushed in order of definition, while gcc4 and above are variable), optimization level, target processor architecture, stack security, etc., two variables defined adjacent to each other may or may not be adjacent to each other in memory location, and the relationship between them is not fixed. To ensure that two objects are contiguous in memory and have a fixed context, use a struct or array definition.





4 stack operation
The specific steps of the function call are as follows:

1) the main calling function will save the parameters required by the calling function in the runtime stack according to the corresponding function calling convention. This action changes the stack pointer of the program.

Note: the x86 platform pushes parameters into the call stack. The x86_64 platform has 16 universal 64-bit registers, so the first six parameters are usually passed by registers when the function is called, and the rest are passed by the stack.

2) the main calling function transfers the control to the called function (using the call instruction). The return address of the function (the address of the next instruction to be executed) is stored in the program stack (the push operation is implicit in the call instruction).

3) if necessary, the called function will set the frame base pointer and save the register values that the called function wishes to keep unchanged.

4) by modifying the value of the top pointer of the stack, the called function allocates memory space for its local variables in the runtime stack, and stores the local variables and temporary variables of the called function in the direction of low address from the location of the frame base pointer.

5) the called function performs its own task, and may need to access the parameters passed by the main call function at this time. If the called function returns a value, it is usually stored in a specified register (such as EAX).

6) once the called function completes the operation, the stack space allocated for the function's local variables will be released. This is usually the reverse execution of step 4.

7) restore the register value saved in step 3, including the frame base pointer register of the main call function.

8) the called function returns control to the main call function (using ret instructions). Depending on the function call convention used, this operation may also clear previously passed parameters from the program stack.

9) once the main call has regained control, it may be necessary to clear the previous arguments from the stack. In this case, the change to the stack requires restoring the frame base pointer value to the value before step 1.

Step 3 and step 4 often appear together at the beginning of a function call, collectively referred to as prologue; Steps 6 through 8 often appear together at the end of a function call, collectively called the epilogue. The function order and function sequence are the beginning and end assembly code automatically added by the compiler, whose implementation is related to the CPU architecture and the compiler. All operations make up the function call except for step 5, which represents the function entity.

Here are the main instructions for function calls.

Push: the pointer ESP at the top of the stack is reduced by 4 bytes; The register data (four bytes, not enough to make up for zero) is pushed onto the stack in bytes, and the data is stored in the address unit pointed to by esp-1, esp-2, esp-3, and esp-4 in bytes from high to low.

Pop: the data in the stack pointed by the top pointer ESP is taken back to the register. Stack top pointer ESP increased by 4 bytes.
It can be seen that the register contents are stored in the stack memory (the original register contents remain the same) by pushing the stack operation, and the address of the top of the stack is reduced; The push operation retrieves the register contents from the stack memory (the stored data in the stack will not be reset automatically) and the stack top address increases. The stack top pointer ESP always points to the next available data on the stack.

Call: the current instruction pointer EIP(which points to the next instruction immediately after the call instruction) is pushed onto the stack, so that the next instruction can be resumed upon return. Then set the EIP to point to the beginning of the called function code to jump to the entry address of the called function.

Leave: restores the stack frame of the main call function in preparation for return. This is equivalent to the instruction sequence movl %ebp, %esp(restoring the original esp value to the beginning of the called function stack frame) and popl %ebp(restoring the original ebp value to the main function frame base pointer).

Return (ret) : used in conjunction with the call instruction to return from a function or procedure. The return address (the next instruction address saved by the previous call instruction) pops up from the top of the stack into the EIP register, where the program goes to continue execution (at which point ESP points to the first parameter entered into the function). With an immediate number, ESP is added to the immediate number (dropping some of the arguments pushed before the call is executed). Before using this instruction, make sure that the contents of the current stack top pointer point to the exact return address saved by the previous call instruction.

Based on the above instructions, the typical function order and function postscript of the called function using the C calling convention are implemented as follows:



Instruction sequence

meaning

The function sequence

(prologue)

Push % ebp

Stack the frame base pointer of the main call function %ebp, that is, save the frame base pointer in the old stack frame so that the old stack frame can be restored when the function returns

Esp, mov % % ebp

Assigns the stack top pointer %esp of the main call function to the frame base pointer %ebp of the called function. At this point, %ebp points to the starting address (bottom of the stack) of the new stack frame of the called function, which is the top of the stack after the old %ebp is pushed

Sub < n > % esp

Opens up stack space for the called function local variable by subtracting the specified number of bytes from the stack top pointer %esp. <n> is an immediate number and usually an integer multiple of 16 (it may be more wasteful than the total number of bytes in the local variable, but GCC USES this rule to ensure strict alignment of data to effectively use various optimization compilation techniques)</n>

Push the < r >

Optional. The called function is responsible for saving certain register (%edi/%esi/%ebx) values if necessary

Function colophon

(epilogue)

Pop < r >

Optional. The called function is responsible for restoring certain register (%edi/%esi/%ebx) values if necessary

Mov % ebp, % esp *

Restores the pointer %esp to the top of the stack of the main calling function and points it to the bottom of the stack of the called function. At this point, the stack space occupied by the local variable is freed, but the contents of the variable are not cleared (skip the processing)

Pop % ebp *

The frame base pointer of the main call function %ebp is pushed, that is, the bottom of the main call function is restored. At this point, the stack top pointer %esp points to the main call function stack top (esp esp-4), the return address store

ret

From the top of the stack, the return address of the main call function pressed on the stack is popped into the instruction pointer register %eip, and execution continues at the location of the main call function. Then the main call function restores to the stack before the call

* : these two instruction sequences can also be implemented by the leave instruction, in which way the compiler decides.

If the local variable registers EDI, ESI, and EBX are not used in both the main and the key functions, the compiler does not need to push them in the function order to improve the execution efficiency of the program.

The parameter push instruction varies from compiler to compiler, and the following two push modes are basically equivalent:

Extern CdeclDemo(int w, int x, int y, intz); // call the CdeclDemo function

CdeclDemo (1, 2, 3, 4); // call the CdeclDemo function

Way to push a stack

Push the stack way two

Pushl 4 // pressing parameter z

Pushl 3 // pressing parameter y

Pushl 2 // pressing parameter x

Pushl 1 // pressing parameter w

Call CdeclDemo // call the function

Addl $16, %esp // restores the original esp value to point to the return address saved before the call

Subl $16, %esp // multiple calls are executed only once

Movl $4, 12(%esp) // passes the parameter z to the fourth position on the stack

Movl $3, 8(%esp) // passes the parameter y to the third position on the stack

Movl $2, 4(%esp) // passes the parameter x to the second position on the stack

Movl $1, (%esp) // passes the parameter w to the top of the stack

Call CdeclDemo // call the function
Both stack pressing methods follow the C call convention, but the main function in mode 2 does not explicitly clean up the stack space after the call returns. Because the compiler pre allocates memory space (sub instruction) for function parameters at the top of the stack in the called function order stage. The function parameters are copied to the stack (rather than being pushed into the stack), and the top pointer is not modified, so the main calling function does not need to modify the top pointer when calling back. The gcc3.4 (or higher) compiler uses this technology to transfer function parameters to the stack, which is more efficient than that the top of stack pointer moves down many times with each parameter stack pressing. When several functions are called consecutively, mode 2 only needs to allocate parameter memory once in advance (the size is enough to accommodate the parameter size and the largest function), and subsequent calls do not need to recover the top of stack pointer every time. Note that when a function is called, both ways make the top of stack pointer point to the leftmost parameter of the function. This paper no longer distinguishes the two stack pressing methods, and the points mentioned in "stack pressing" or "stack pushing" are understood according to the corresponding assembly code. If there is no assembly, it refers to method 2.
In some cases, the compiler generated function call entry / exit instruction sequence does not follow the above method. For example, if the C function is declared as static (visible only in this compilation unit) and the function is called directly in the compilation unit, without being shown or implicitly addressed (i.e. no function pointer points to the function), the compiler is sure that the function will not be called by other compilation units, so it is free to modify its in / out instruction sequence to achieve optimization.
Although the register names and instructions used are different in different processor architectures, the basic process of creating stack frames is the same.
Note that stack frame is a runtime concept, and if the program is not running, there is no stack and stack frame. However, by analyzing the assembly code (especially the function sequence and the function postscript process) to build the function stack frame in the target file, the stack frame structure of the function can be understood even if the function is not running. Through the analysis, we can determine the accurate value of the local variable space allocated on the function stack frame, whether to use the frame base pointer in the function, and identify all memory references to variables in the function stack frame.
From:
http://www.cnblogs.com/clover-toeic/p/3755401.html


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.