C语言函数调用过程(汇编分析)
C语言函数调用过程(汇编分析)
C语言函数调用过程(汇编分析)
函数调用主要的三个方面是函数名、参数列表和返回值,想要深入了解函数的调用机制,就需要深入底层,分析源文件的汇编码来分析函数调用的流程
函数调用的步骤如下:
函数参数传入
函数栈帧开辟
函数返回值
函数栈帧回退
所谓栈帧,就剩为一个函数调用单独分配的栈空间
图片来源:简书金戈大王
函数参数的传入和函数的返回值很好理解,它们函数输入和输出,但是函数栈帧的开辟和回退的具体作用又是怎样的呢?
函数的本意是可以重复使用的代码块,在CPU执行程序时,是逐行执行的;如果遇到函数调用,CPU会记录下当前代码块中的地址,然后跳转到函数中,当函数体执行完毕周,CPU会再次返回到当初记录的位置,继续执行后续代码
Ubuntu下查看源文件的反汇编码的查看方式如下所示:
终端输入:gcc -S filename.c
之后只需使用cat命令即可查看反汇编码
例:源程序内容如下:
#include
void fun(int a)
{
a += 1;
printf("Hello World!\n");
}
int main()
{
int val = 1;
fun(val);
return 0;
}
该程序的汇编码如下所示:(本人汇编小白,如有错误,还请大家指正)
.file"func.c"
.text
.section.rodata
.LC0:
.string"Hello World!"
.text
.globlfun
.typefun, @function
fun:
.LFB0:
.cfi_startproc
pushq%rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq%rsp, %rbp
.cfi_def_cfa_register 6
subq$16, %rsp
movl%edi, -4(%rbp)
addl$1, -4(%rbp)
leaq.LC0(%rip), %rdi
call[email protected]
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.sizefun, .-fun
.globlmain
.typemain, @function
main:
.LFB1:
.cfi_startproc
pushq%rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq%rsp, %rbp
.cfi_def_cfa_register 6
subq$16, %rsp
movl$1, -4(%rbp)
movl-4(%rbp), %eax
movl%eax, %edi
callfun
movl$0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.sizemain, .-main
.ident"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section.note.GNU-stack,"",@progbits
代码中以 . 开头的代码行属于链接时使用的辅助信息,在实际中不会执行,因此可以将其删除提高代码阅读性
fun:
pushq%rbp
movq%rsp, %rbp
subq$16, %rsp
movl%edi, -4(%rbp)
addl$1, -4(%rbp)
call[email protected]
nop
leave
ret
main:
pushq%rbp
movq%rsp, %rbp
subq$16, %rsp
movl$1, -4(%rbp)
movl-4(%rbp), %eax
movl%eax, %edi
callfun
movl$0, %eax
leave
ret
汇编代码分析:
函数的通用格式如下所示:
函数名:
pushq%rbp
movq%rsp, %rbp
函数体(具体代码)
leave
ret
首先需要明确的是,rsp为栈顶寄存器,rbp为栈底寄存器,push为在栈顶寄存器存放数据,栈顶上移
leave相当于
movl%ebp, %esp
pop1%ebp
其作用为恢复堆栈指针,具体步骤如下:使用movl将当前栈指针指向当前帧的起始位置,之后再恢复帧指针,这样,popl调用之后,程序将完全退出当前帧
根据以上汇编码可以分析函数执行过程如下:
每次call一个函数,函数总是先把当前的栈底指针压入堆栈,然后把栈底指针移动到当前栈顶,这样就相当于在旧栈上新开辟一个栈,等被调用的函数执行的时候,CPU依次执行栈中的指令,然后再依次出栈,也就是说,当函数执行完毕,再恢复堆栈指针之后,栈顶指针又回到了函数调用的位置
C语言函数调用过程(汇编分析)相关教程