从汇编角度看Linux C函数的调用约定和参数传递的细节
2016-11-09 00:12
399 查看
x86架构下,函数执行借助于 hardware stack。为了不同模块函数能在runtime时可以互相调用,程序必须遵守共同的的Calling Convention,这也是ABI的一部分。推荐两本参考资料:
x86 Assembly Guide
Computer Systems: A Programmer’s Perspective
从汇编看,完成一个函数调用关键执行就是
caller 的 rip+1 入栈, rsp -= 8 (x64)
caller 的 rbp 入栈, rsp -= 8
callee 的 rsp 减小,开辟新栈帧, rsp -= callee实际需要栈帧大小
callee 的 rsp 增大,收回新栈帧, rsp 保存的 base addr 赋给rsp
caller 的 rbp 出栈,rsp += 8,
caller 的 rip+1 出栈, rsp += 8
这个可以通过 gdb 调试来直观的观察,例子见文章最后,编译
下面的例子描述了Linux C程序的函数传参与调用约定 Calling Convention :
对应的汇编代码如下, 用
x86 Assembly Guide
Computer Systems: A Programmer’s Perspective
从汇编看,完成一个函数调用关键执行就是
call, pushd, leave, ret等指令, 一个函数调用的入栈出栈大致如下:
caller 的 rip+1 入栈, rsp -= 8 (x64)
caller 的 rbp 入栈, rsp -= 8
callee 的 rsp 减小,开辟新栈帧, rsp -= callee实际需要栈帧大小
callee 的 rsp 增大,收回新栈帧, rsp 保存的 base addr 赋给rsp
caller 的 rbp 出栈,rsp += 8,
caller 的 rip+1 出栈, rsp += 8
这个可以通过 gdb 调试来直观的观察,例子见文章最后,编译
gcc -g main.c, 生成
a.out带调试信息的程序。
gdb a.out b main display $rip // 断住时自动显示指令寄存器 rip 的值 display $rbp display $rsp run // 开始执行 layout asm // 打开汇编窗口 si // 下一条汇编 step in next instruction,遇到call进入 // 回车一直单步,即可查看判断
下面的例子描述了Linux C程序的函数传参与调用约定 Calling Convention :
int add(int a, int b, int c, int d, int e, int f, int g, int h) { return a + b + c + d + e + f + g + h; } int main() { int a = 1; int b; b = add(a, 2, 3, 4, 5, 6, 7, 8); return 0; }
对应的汇编代码如下, 用
gcc -S main.c编译得到,删除了点开头的标志,默认汇编风格是 AT&T风格的。
add: pushq %rbp ; rbp -> [rsp], rsp -= 8, caller栈帧base addr入栈 movq %rsp, %rbp ; rsp -> rbp, 新栈帧 base addr movl %edi, -4(%rbp) ; arg1 -> 局部变量(栈帧base addr + offset) movl %esi, -8(%rbp) ; arg2 movl %edx, -12(%rbp) ; arg3 movl %ecx, -16(%rbp) ; arg4 movl %r8d, -20(%rbp) ; arg5 movl %r9d, -24(%rbp) ; arg6 movl -8(%rbp), %eax ; arg2 -> eax movl -4(%rbp), %edx ; arg1 -> edx addl %eax, %edx ; eax + edx -> edx (arg2 + arg1) movl -12(%rbp), %eax ; arg3 -> eax addl %eax, %edx ; eax + edx -> edx (arg2 + arg1 + arg3) movl -16(%rbp), %eax ; arg4 -> eax addl %eax, %edx ; eax + edx -> edx movl -20(%rbp), %eax ; arg5 -> eax addl %eax, %edx ; eax + edx -> edx movl -24(%rbp), %eax ; arg6 -> eax addl %eax, %edx ; eax + edx -> edx movl 16(%rbp), %eax ; arg7 -> eax, (第7个参数在caller的栈帧里) addl %eax, %edx ; eax + edx -> edx movl 24(%rbp), %eax ; arg8 -> eax, (第8个参数在caller的栈帧里) addl %edx, %eax ; edx -> eax, 返回值放在eax popq %rbp ; [rsp] -> rbp, rsp -= 8 ret ; [rsp] -> rip main: pushq %rbp movq %rsp, %rbp subq $32, %rsp ; 开辟栈32 movl $1, -8(%rbp) ; 保存局部变量 movl -8(%rbp), %eax ; 局部变量 -> eax movl $8, 8(%rsp) ; 第8个参数放到栈里 movl $7, (%rsp) ; 第7个参数放到栈里 movl $6, %r9d ; arg6 -> r9d movl $5, %r8d ; arg5 -> r8d movl $4, %ecx ; arg4 -> ecx movl $3, %edx ; arg3 -> edx movl $2, %esi ; arg2 -> esi movl %eax, %edi ; arg1 -> edi call add ; rip++ -> [rsp], rsp -= 8, 调用完毕函数后的下一条指令入栈 movl %eax, -4(%rbp) ; 存返回值由寄存器eax到局部变量 movl $0, %eax ;0 -> eax leave ; rbp -> rsp, [rsp] -> rbp, rsp += 8 ret ; [rsp] -> rip
相关文章推荐
- x64 调用约定,参数传递以及函数返回值
- 从汇编角度查看C语言函数调用约定
- x86 和 x64 汇编调用C 函数参数传递规则(GCC)
- arm汇编程序调用C函数之参数传递
- 调用约定的参数传递顺序
- 汇编中调用printf:传递参数超过4个,用栈传递参数
- arm汇编程序调用C函数之参数传递
- x86 和 x64 汇编调用C 函数参数传递规则(GCC)
- 汇编调用C函数--利用堆栈传递参数
- 关于C语言中函数调用和参数传递机制的探讨--汇编
- 一个小程序引发的讨论(运算优先级、参数传递与调用约定的问题)
- 用内嵌汇编来解决运行时参数不确定的函数的调用问题
- 终于解决了--调用存储过程提示传递参数多的问题!
- 汇编中参数的传递和堆栈修正
- Ajax基石脚本异步并发调用参数传递
- 关于delphi 的函数调用和参数传递方式深入研究之疑惑
- Delphi中调用C约定不定参数函数的调用方法.
- 关于Java中方法调用时参数的传递
- 关于函数调用中参数传递的一些思考
- 汇编中参数的传递和堆栈修正