The caller pushes the function's parameters on the stack, one after another, in reverse order (right to left, so that the first argument specified to the function is pushed last ). the caller then executes CALL
Instruction to pass control to the callee. This CALL
Is either near or far depending on the memory model. the callee controls es control, and typically (although this is not actually necessary, in functions which do not need to access their parameters) starts by saving the value SP
In BP
So as to be able to use BP
As a base pointer to find its parameters on the stack. However, the caller was probably doing this too, so part of the calling convention states that BP
Must be preserved by any C function. Hence the callee, if it is going to set up BP
AsFrame pointer, Must push the previous value first. The callee may then access its parameters relative BP
. The word [BP]
Holds the previous value BP
As it was pushed; the next word, [BP+2]
, Holds the offset part of the return address, pushed implicitly CALL
. In a small-model (near) function, the parameters start after that, [BP+4]
; In a large-model (far) function, the segment part of the return address lives [BP+4]
, And the parameters begin [BP+6]
. The leftmost parameter of the function, since it was pushed last, is accessible at this offset from BP
; The others follow, at successively greater offsets. Thus, in a function such printf
Which takes a variable number of parameters, the pushing of the parameters in reverse order means that the function knows where to find its first parameter, which tells it the number and type of the remaining ones. the callee may also wish to decrease SP
Further, so as to allocate space on the stack for local variables, which will then be accessible at negative offsets from BP
. The callee, if it wishes to return a value to the caller, shocould leave the value in AL
, AX
Or DX:AX
Depending on the size of the value. Floating-point results are sometimes (depending on the compiler) returned in ST0
. Once the callee has finished processing, it restores SP
From BP
If it had allocated local stack space, then pops the previous value BP
, And returns RETN
Or RETF
Depending on memory model. When the caller regains control from the callee, the function parameters are still on the stack, so it typically adds an immediate constant SP
To remove them (instead of executing a number of slow POP
Instructions). Thus, if a function is accidentally called with the wrong number of parameters due to a prototype mismatch, the stack will still be returned to a sensible state since the caller, whichKnowsHow many parameters it pushed, does the removing. reference the call process example and test cases from the NASM Manual/* Take three parameters as an example. In AT & T32 assembly, the stack increases from fun to low address (p0, p1, p2); 1. parameter stack entry, right-> leftgcc practice: Where p0 p1 p2 stands for immediate count movl $ p2 8 (% esp) movl $ p1 4 (% esp) movl $ p0 (% esp) | _______ | ebp-> | _______ | |__ p2 ___ | |__ p1 ___ |__ p0 ___ | <-esp | _______ | _______ | 2. call fun | _______ | ebp-> | _______ | |__ p2 ___ | |__ p1 ___ |__ p0 ___ |__ ret __| <-esp | _______ | 3. jump to fun code push % eb Pmovl % esp, % ebp | _______ | |__ p2 ___ | |__ p1 ___ | |__ p0 ___ |__ ret __| ebp-> |__ ebp __| <-esp | _______ | the stored ebp is the ebp in step 1, that is, the previous ebp, not the ebp on the left of the figure. If the stack grows to a low address, the old ebp = [ebp] ret = [ebp + 4] p0 = [ebp + 8] p1 = [ebp + 12] p2 = [ebp + 16] 4. decrease ESP further, so as to allocate space on the stack for local variables, which will then be accessible at negative offsets from EBP. su Bl $24, % esp allocate stack space as needed, example 24/4 = 6 4B local variables | _______ | |__ p2 ___ | |__ p1 ___ |__ p0 ___ |__ ret __ | ebp-> |__ ebp __| | _______ | <= esp + 12 = ebp-12 | _______ | <-esp | _______ | 5. returns the 5.1 adjustment Stack pointer Method 1: addl $24, % esp Method 2: movl % ebp, % esp | _______ | |__ p2 ___ | |__ p1 ___ | |__ p0 ___ |__ ret __| ebp-> |__ ebp __| <-esp | _______ | _______ | 5.2 the old frame pointer = destroy the current stack frame popl % ebp | _______ | ebp-> | _______ | |__ p2 ___ | |__ p1 ___ | |__ p0 ___ |__ ret __| <-esp |__ ebp __ | _______ | 5.3 return the caller ret | _______ | ebp-> | _______ | __p2 ___ | |__ p1 ___ | |__ p0 ___ | <-esp |__ ret __| |__ ebp __| |_______ | | _______ | The esp must be adjusted to remove the parameter. Gcc did not do this step. Esp is reserved here. When the function is called again below, the old value will be overwritten. See step 1! Steps 5.1 and 5.2 can be replaced by the leave command * // verification # include
# Include
Void fun (int a, int B) {int local; unsigned int I, j; long ret; local = 12; a = 10; // printf ("% d \ n", a); # if 1asm volatile ("movl % ebp, % eax \ n \ t" "movl % esp, % ebx \ n \ t ":" = a "(I)," = B "(j) :); printf (" ebp = % p; esp = % p \ n ", I, j); printf (" local auto var: % d \ n ", I-j ); // space occupied by local variables asm volatile ("movl 12 (% ebp), % eax \ n \ t" // parameter B "movl 8 (% ebp ), % ebx \ n \ t "// parameter a" movl 4 (% ebp), % ecx \ n \ t "// ret: "= a" (I), "= B" (j), "= c" (ret) :); printf ("p2 = % x; p1 = % x; ret = % p \ n ", I, j, ret); # endif} int main (int argc, char * argv []) {fun (1, 2 ); exit (0 );}
Summary1-if you compile the Assembly on your own, you need to handle the work yourself, as well as the protection measures for the pressure stack out of some registers
2-step 5.2: When the ebp pops up, a stack frame does not exist, that is, the function environment has been revoked. Therefore, if you return a local variable reference in fun, it will be meaningless. This is the most common problem !!!
3-local variable space allocation. In other words, the size of esp should be determined by the symbol table during compilation!