call和ret指令
如何访问栈帧、如何切换栈帧、如何传递参数和返回值
- call、ret指令作用:
- call:1)将IP(即PC)旧值压栈保存(保存在函数的栈帧顶部);2)设置IP新值,无条件转移至被调用函数的第一条指令
- ret:从函数的栈帧顶部找到IP旧值,将其出栈并恢复IP寄存器
- 如何访问栈帧里的数据
- 访问当前函数的局部变量:
[ebp-4]、[ebp-8]...
- 访问上一层函数传过来的参数:
[ebp+8]、[ebp+12]...
- 访问当前函数的局部变量:
- 如何切换栈帧(函数调用的时候)
- 函数调用时发生的切换
- call指令
- 将IP旧值压栈保存:相当于push IP
- 设置IP新值,无条件转移至被调用函数的第一条指令:相当于jmp add(add是调用的函数名)
- 每个函数开头固定的例行处理(相当于执行指令
enter
):会导致每个栈帧底部(ebp),都保存着上一层栈帧的基址(ebp)push ebp
:保存上一层函数的栈帧基址(ebp旧值)mov ebp,esp
:设置当前函数的栈帧基址(ebp新值)
- call指令
- 函数返回时发生的切换
- 每个函数ret前的固定例行处理(相当于执行指令
leave
)mov esp,ebp
:让esp指向当前栈帧的底部pop ebp
:将esp所指元素出栈,并写入寄存器ebp
- ret指令:从函数的栈顶找到IP旧值,将其出栈并恢复IP寄存器
- 每个函数ret前的固定例行处理(相当于执行指令
- 宏观看一下一个函数的整体汇编代码框架:
- 函数调用时发生的切换
- 如何传递参数和返回值
- 栈帧内包含的内容:
- 栈帧最底部:上一层栈帧基址(ebp旧值) 【一定存在】
- 栈帧底部:局部变量(离着本层函数的ebp近)【不一定存在】
- 空闲区域:因为gcc编译器将每个栈帧大小设置为16B的整数倍(当前函数的栈帧除外)【不一定存在】
- 中间:部分寄存器值(调用其他函数前,如果有必要,可将某些寄存器的值入栈保存,防止中间结果被破坏)【不一定存在】
- 栈帧顶部:调用参数(离着下一层被调用函数的ebp近)【不一定存在】
- 栈帧最顶部:当前函数的返回地址(函数调用时,call指令将IP寄存器值,即返回地址,压栈保存)【一定存在】
- 如何传递参数:在call指令之前,将调用参数写入栈帧顶部区域
- 如何传递返回值:在ret指令之前,将函数返回值写入eax寄存器
- 栈帧内包含的内容: