函数调用是很好理解也很强大的编程技术,我们也是用了不知道多少次。那如果我们想玩点hack一点的东西,比如搞点寄存器快照,调用一个函数就可以将当前函数执行的状态全都保存下来,并且后续让它可以恢复,典型的setjmp
函数,就需要更深入地理解在在汇编语言的视角里,它是如何实现的
c语言程序函数调用
1 | //test.c |
1 | gcc test.c -o test |
1 | 0000000000001129 <add>: |
可以看到,在进行函数调用后,add函数并不是第一时间直接执行业务指令,而是调整了%rbp
寄存器
1 | 112d: 55 push %rbp |
另外call
指令等价于
1 | push ip |
所以c语言编译而来的程序,函数在执行第一句实际操作的代码之前,(从高地址到低地址)函数栈里已经存放了
- 调用者调用时的ip寄存器内容(返回地址)
- 调用者的rbp寄存器内容(栈基)
汇编程序函数调用
汇编函数的调用稍有不同,c语言因为有编译器自动生成,在call
之后会自动进行
1 | pushq %rbp |
操作,借位也会有ret
指令,但是汇编不同,汇编函数在进行call
调用后,只有rip寄存器被保存在了栈中,%rsp
%rbp
寄存器都还处于是调用者的形状
c语言内嵌汇编调用
1 | int save(struct Context *cxt) { |
1 | 0000000000001149 <save>: |
截取了一段仿照setjmp
写的save
汇编,并改成内嵌汇编后编译,发现与标准汇编果然不同,还是加上了开头的push %rbp
和mov %rsp, %rbp
,自然,原本按照单纯call
指令的栈帧信息设计的save
已经失效了,要做一些调整才能正常工作,详见DIY-setjmp
- 本文作者: 汤圆
- 本文链接: https://littlesun.cloud/2023/07/27/汇编层面的函数调用/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!