Today, I suddenly want to analyze the changes in stack frames in the process of mutual function calls. I still want to describe this process with a clear idea as much as possible, it is important to understand the c function call principle.
1. Stack
First of all, it is also very important to make it clear that the stack grows down. The so-called downward growth refers to the extension from the path of the memory high address> local address, the stack has a stack bottom and a stack top, so the stack top address is lower than the stack bottom. For the CPU Of The x86 system
---> The register ebp (base pointer) can be called "frame pointer" or "base address pointer". In fact, the meaning is the same.
---> Register esp (stack pointer) can be called a "stack pointer ".
You need to know:
---> Before being changed, ebp always points to the beginning of the stack frame, that is, the bottom of the stack. Therefore, ebp is used for addressing in the stack.
---> Esp moves along with the data's inbound and outbound stacks. That is to say, esp always points to the top of the stack.
See, if function A calls function B, we call function A as "Caller", and function B as "called", the function call process can be described as follows:
(1) first, the caller (A)'s stack base address (ebp) is put into the stack to save the previous Task information.
(2) then, the value of the caller (A) stack top pointer (esp) is assigned to ebp as the new base address (that is, the bottom of the stack of the caller B ).
(3) then, the corresponding space (generally using sub commands) is opened on the base address (the bottom of the stack of caller B) for the stack space of consumer B.
(4) After function B returns, the ebp of the current stack frame is restored to the top stack (esp) of caller A, so that the top of the stack is restored to the position before function B is called; then caller A can pop up the previous ebp value from the restored stack top (this can be done because the value is pushed into the stack one step before the function call ). In this way, both ebp and esp restore the position before function B is called, that is, the stack restores the status before function B is called.
This process is completed in AT&T assembly using two commands:
Leave
Ret
The two commands are more straightforward:
Mov % ebp, % esp
Pop % ebp
2. Let's take a simple example and look at function calls from the perspective of assembly.
2.1 create a simple program named main. c
Development and testing environment:
Ubuntu 12.04
Gcc version: 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) (comes with Ubuntu)
<SPAN style = "FONT-SIZE: 18px">/* main. c code: */void swap (int * a, int * B) {int c; c = * a; * a = * B; * B = c ;} int main (void) {int a; int B; int ret; a = 16; B = 64; ret = 0; swap (& a, & B ); ret = a-B; return ret ;}</SPAN>
2.2 compile
# Gcc-g-o main. c
# Objdump-d main> main. dump
# Gcc-Wall-S-o main. s main. c
In this way, you can see main. s or main. dump. Here we choose to use main. dump.
Take the key part, that is, _ start, swap, and main. Why is there _ start, because the ELF format entry is actually _ start rather than main (). The following figure shows the stack space structure before and after the main () function calls swap. The number on the right represents the number of offset bytes relative to the frame pointer. Later we will use GDB for debugging and we will find that the stack changes are consistent.
(!!! Please note that due to stack alignment, the compiler may have useless memory addresses when allocating stack space, and these unused memory addresses are not displayed, so we can only understand the function stack frame structure !! The specific stack memory content is subject to the following GDB debugging information !!!)