您的位置:首页 > 运维架构 > Linux

从汇编角度看Linux C函数的调用约定和参数传递的细节

2016-11-09 00:12 399 查看
x86架构下,函数执行借助于 hardware stack。为了不同模块函数能在runtime时可以互相调用,程序必须遵守共同的的Calling Convention,这也是ABI的一部分。推荐两本参考资料:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: