协程的汇编实现
1 寄存器
X86-64有16个64位寄存器,分别是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp, %r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15
。其中:
%rax
作为函数返回值使用%rsp %rbp
栈指针寄存器,指向当前函数栈的栈顶和栈底%rdi,%rsi,%rdx,%rcx,%r8,%r9
用作函数参数,依次对应第1-6参数%rbx,%rbp,%r12,%r13,%r14,%r15
用作数据存储,遵循被调用者使用规则,简单说就是随便
用,调用子函数之前要备份它,以防他被修改%r10,%r11
用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值%rip
:用于存放下一条指令地址,CPU会取此寄存器的地址去找到下一条指令并执行。- 关于寄存器可以参考csapp中的图片:
2 函数调用
函数调用是从一个函数栈跳转到相邻的另一个函数栈。由于调用返回后还需恢复原函数栈的状态,因此必须在调用时通过寄存器和栈空间的配合来存储一些数据,方便调用完成后恢复。具有调用关系的两个函数栈空间一定是相邻的,也就是说主调方的函数栈空间与被调方的函数栈空间一点是相邻的。
add.c
int add(int a, int b){
return a+b;
}
int main(){
int a = 1;
int b = 2;
int x = add(a,b);
return 0;
}
add.s
.file "add.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
pushq %rbp ;将main函数帧的起始位置压栈,记在add函数帧里
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp ;将add函数帧的栈顶位置赋给%rbp,没有像main修改%rsp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp) ;第一个参数放入add函数帧
movl %esi, -8(%rbp) ;第二个参数放入add函数帧
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax ;结果交给%eax做为返回值
popq %rbp ;%rbp恢复为main函数帧的起始位置
.cfi_def_cfa 7, 8
ret ;把main函数的返回地址赋值给%rip,下一步CPU会调到该地址的指令执行
.cfi_endproc
.LFE0:
.size add, .-add
.globl main
.type main, @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 ;%rsp下移16,为main函数栈预留空间
movl $1, -12(%rbp)
movl $2, -8(%rbp)
movl -8(%rbp), %edx
movl -12(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call add ;调用add函数,此步骤会将main函数的返回地址压入栈,然后将add函数入口地址赋值给%rip,下一步CPU就会执行到add函数
movl %eax, -4(%rbp)
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident