|
?
函数调用处理过程
?
The C calling convention in 16-bit programs is as follows. In the following description, the words caller and callee are used to denote the function doing the calling and the function which gets called.
- 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 a
CALL
instruction to pass control to the callee. This
CALL
is either near or far depending on the memory model.The callee receives control, and typically (although this is not actually necessary, in functions which do not need to access their parameters) starts by saving the value of
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
as a frame pointer, must push the previous value first.The callee may then access its parameters relative to
BP
. The word at
[BP]
holds the previous value of
BP
as it was pushed; the next word, at
[BP+2]
, holds the offset part of the return address, pushed implicitly by
CALL
. In a small-model (near) function, the parameters start after that, at
[BP+4]
; in a large-model (far) function, the segment part of the return address lives at
[BP+4]
, and the parameters begin at
[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 as
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, should 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 of
BP
, and returns via
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 to
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, which knows how many parameters it pushed, does the removing. 引用自NASM手册 调用过程示例及测试用例
/*
以三个参数为例子,AT&T32汇编,栈向低地址增长
fun(p0, p1, p2);
1.参数入栈,right->left
gcc的做法:
其中p0 p1 p2姑且认为是立即数
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.跳转到fun代码
push %ebp
movl %esp, %ebp
|_______|
|_______|
|_______|
|_______|
|__p2___|
|__p1___|
|__p0___|
|__ret__|
ebp->|__ebp__|<-esp
|_______|
|_______|
其中存储的ebp是第2步中的ebp,即之前的ebp,不是图左面的ebp
如果栈向低地址增长
那么
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.
subl $24, %esp
根据具体需要分配栈空间,这里示例24/4=6个4B局部变量
|_______|
|_______|
|_______|
|_______|
|__p2___|
|__p1___|
|__p0___|
|__ret__|
ebp->|__ebp__|
|_______|
|_______|
|_______|<= esp + 12 = ebp - 12
|_______|
|_______|
|_______|<-esp
|_______|
|_______|
5.返回
5.1 调整栈指针
方式1:addl $24, %esp
方式2:movl %ebp, %esp
|_______|
|_______|
|_______|
|_______|
|__p2___|
|__p1___|
|__p0___|
|__ret__|
ebp->|__ebp__|<-esp
|_______|
|_______|
|_______|
|_______|
|_______|
|_______|
|_______|
|_______|
5.2弹出旧frame指针=销毁当前栈帧
popl %ebp
|_______|
ebp->|_______|
|_______|
|_______|
|_______|
|__p2___|
|__p1___|
|__p0___|
|