从汇编角度看Linux C函数的调用约定和参数传递的细节
x86架构下,函数执行借助于 hardware stack。为了不同模块函数能在runtime时可以互相调用,程序必须遵守共同的的Calling Convention,这也是ABI的一部分。推荐两本参考资料:x86 Assembly GuideComputer Systems: A Programmer’s Perspective从汇编看,完成一个函数调用关键执行就是 call, pushd,
·
x86架构下,函数执行借助于 hardware stack。为了不同模块函数能在runtime时可以互相调用,程序必须遵守共同的的Calling Convention,这也是ABI的一部分。推荐两本参考资料:
从汇编看,完成一个函数调用关键执行就是 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
更多推荐
已为社区贡献9条内容
所有评论(0)