Stack, stack frame Stack Frames and function call process Control Flow

The stack is actually a small piece of computer system memory. The stack is a special memory area. The growth direction of the stack in the memory is to expand to the lower address. The %rsp register stores the lowest address of the stack, that is, the address of the top element of the stack. The application of this stack structure in the program helps to realize functions such as function calling, management of local variables, and recursion.

 Push and Pop commands

The stack in the memory can perform push and pop instructions. If you have learned the stack in the data structure, it is basically similar. 

Pushq

pushq SrcOperation consists of the following steps:

  1. SrcGet operands from .
  2. Decrease the value of the %rsp register by 8 (because in the x86-64 architecture, the length of a binary word is 8 bytes).
  3. Write the operand to the address pointed to by the %rsp register.

Through pushqthe operation, we push the data to the top of the stack and update the top pointer %rsp

popq

popq DestOperation consists of the following steps:

  1. Read the value at the address pointed to by the %rsp register.
  2. Add 8 to the value of the %rsp register. (The higher the address, the higher the address, here is the upward reduction)
  3. Store the read value in Dest(usually a register).

The thing to note about pop is that we just copied the original value to Dest. Although the value of %rsp has been moved up by adding 8, the original value pointed to by %rsp still exists in the memory location in .

Program control flow Control Flow

During program execution, the stack structure is used to support function calls and returns.

Function call: call instruction

When you need to call a function, use call labela directive. It does the following:

  1. Push the return address onto the stack. call(The return address is the address of the next instruction immediately after the instruction. This is well understood. After the call is executed, it is necessary to return and continue to execute.)
  2. Jump (jump) to labelthe specified function start position.

Function returns: ret instruction

Directives are used when a function has finished executing and needs to return to where it was called ret. It does the following:

  1. Pop address from the stack (pop address from stack).
  2. Jump to the popped address.

Here is an example, I have added a comment, it should be easy to understand. In the figure, %rip stores the address of the currently executed instruction, that is, the instruction pointer.

 Stack stores extra parameters

When we call a function, if the parameters of the function are less than or equal to 6, we can use the registers %rdi %rsi %rdx %rcx %r8 %r9 to store the six parameters respectively, but if there are more than 6 parameters, Starting from the seventh, we'll put them on the stack.

Stack Frames stack frame

What is a stack frame? When calling a function,  call directives are used. call The instruction pushes the return address onto the stack and then jumps to the start of the target function. At this point, a new stack frame will be allocated and set up to store parameters, local variables, etc. of the target function.

The return address belongs to the caller's (previous function's) stack frame. When a function is called, its return address is callpushed onto the top of the current stack frame by the instruction, followed by the stack frame of the next (called) function.

Stack-based programming languages ​​usually support recursion, such as C, Pascal, and Java. The code of these programming languages ​​must be "reentrant" (Reentrant), which means that a single function can have multiple simultaneous instances. In order to implement this functionality, we need some way to store the state of each function instance, including parameters, local variables, and return pointers. Stack discipline (Stack Discipline) is a strategy adopted to achieve the above requirements. For a given function instance, we only need to maintain its state for a limited amount of time, from the time the function is called until the function returns. Also, the called function (callee) needs to return before the calling function (caller) returns.

To implement stack discipline, we allocate the stack as stack frames . Each stack frame represents the state of a single function instance. When a function is called, a new stack frame is allocated for it to store its parameters, local variables, return pointer, etc. When a function returns, its stack frame is freed, making room for other function instances.

A stack frame is a data structure used to store the state of a function instance. Each stack frame contains the following:

  1. Return information: including the return address (return address) and possible saved register values.
  2. Local storage (if needed): Used to store local variables within functions.
  3. Temporary space (if needed): Used to store temporary data that may be needed during function execution.

The allocation and recovery of the stack frame is done during the call and return of the function:

  1. Allocation space: When entering a function, the return address is pushed onto the stack first, and then a stack frame is allocated for it. This process includes pushqinstructions ( callexecuted by instructions).
  2. Reclaim space: When a function returns, its stack frame is freed. This process consists of popqinstructions ( retperformed by instructions) popping the return address from the stack.

The caller frame above is the caller's stack frame, and the caller calls another function, so there is a new stack frame.

Caller Saved and Callee Saved Registers

Finally, add two more concepts: Caller Saved and Callee Saved.

When a function uses another function, the other function will also use the register to store data when performing calculations, which will inevitably affect the value of the previous function on the register, so there is such a specification or convention to help Programs use registers judiciously without conflicting with each other.

Caller-saved register (also known as "Call-Clobbered")

What this term means is that it is the caller's responsibility to save the values ​​of these registers before calling the procedure (function or subroutine). This is because the callee may modify the values ​​of these registers without being responsible for restoring them. In other words, if the caller cares about the values ​​of these registers, it needs to save those values ​​to memory before the calling procedure, and restore them to the registers after the calling procedure completes and returns.

The registers belonging to the caller-saved register are: %rdi %rsi %rdx %rcx %r8 %r9 %r10 %r11

Callee-saved register also called ("Call-Preserved")

When a function or subroutine (callee) plans to use these registers, it is responsible for preserving the initial values ​​of these registers during execution and restoring them before the procedure ends. In this way, the caller can be sure that the values ​​of these registers remain unchanged after the calling procedure returns. The purpose of callee-saved registers is to relieve the caller of the burden of not having to perform save and restore operations for these registers. Thus callee-saved registers and caller-saved registers are complementary.

The registers belonging to the callee-saved register are: %rbx, %r12, %r13, %r14

To put it simply, the caller-saved register will be changed by the sub-function, and the caller needs to save it by storing it in advance; while the callee-saved register can ensure that the value remains unchanged after the call ends, and the caller does not need to worry about them~

Guess you like

Origin blog.csdn.net/weixin_44492824/article/details/131512065