最近在阅读大名鼎鼎的《深入理解计算机系统》,读到第三章,介绍了函数的底层实现。下面来听我娓娓道来。
为了理解,我就用书上的例子,如果在中途有新的术语,我会就近解释。
1. 背景
全文将会围绕下面两个函数来介绍所有的实现机制,这两个函数是:
第一个,主调用函数(它去调用另外一个函数 proc)
long call_proc()
{
long x1 = 1; int x2 = 2;
short x3 = 3; char x4 = 4;
proc(x1, &x1, x2, &x2, x3, &x3, x4, &x4);
return (x1+x2)*(x3-x4);
}
第二个,被调用函数 (它接受其他函数的调用)
void proc(long a1, long *a1p,
int a2, int *a2p,
short a3, short *a3p,
char a4, char *a4p)
{
*a1p += a1;
*a2p += a2;
*a3p += a3;
*a4p += a4;
}
将这个函数分别 设置保存为两个文件,例如以函数名为文件名,proc.c 和 call_proc.c。 用gcc自带的汇编功能将两个文件汇编成汇编文件。例如:
gcc -Og -S call_proc.c
对这两个C文件汇编,将得到两个以 .s 为后缀的汇编文件。如,call_proc.s 和 proc.s 。
2. 汇编文件介绍
下面,将通过介绍上面两个汇编文件来了解底层对函数调用的支持。
按照顺序,先来看看主调用函数 call_proc.s 的内容
.file "p171_call_proc.c"
.text
.globl call_proc
.type call_proc, @function
call_proc:
.LFB0:
.cfi_startproc
subq $40, %rsp
.cfi_def_cfa_offset 48
movq %fs:40, %rax
movq %rax, 24(%rsp)
xorl %eax, %eax
movq $1, 16(%rsp)
movl $2, 12(%rsp)
movw $3, 10(%rsp)
movb $4, 9(%rsp)
leaq 9(%rsp), %rax
pushq %rax
.cfi_def_cfa_offset 56
pushq $4
.cfi_def_cfa_offset 64
leaq 26(%rsp), %r9
movl $3, %r8d
leaq 28(%rsp), %rcx
movl $2, %edx
leaq 32(%rsp), %rsi
movl $1, %edi
movl $0, %eax
call proc
movslq 28(%rsp), %rax
addq 32(%rsp), %rax
movq %rax, %rcx
movswl 26(%rsp), %edx
movsbl 25(%rsp), %eax
subl %eax, %edx
movslq %edx, %rax
imulq %rcx, %rax
addq $16, %rsp
.cfi_def_cfa_offset 48
movq 24(%rsp), %rdi
xorq %fs:40, %rdi
je .L2
call __stack_chk_fail
.L2:
addq $40, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size call_proc, .-call_proc
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits