C++协程在Linux中的汇编实现

文章详细解释了X86-64架构下的寄存器使用,特别是在函数调用中如何通过寄存器和栈空间存储数据以支持协程。同时介绍了gcc内联汇编的应用以及协程栈的测试,展示了函数调用时对栈的操作过程。
摘要由CSDN通过智能技术生成

协程的汇编实现

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	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

3 gcc内联汇编

参考

func.c

#include <stdio.h>
 
int func();

// func 的定义以汇编语言书写
__asm__(
".globl func                            \n\t"
".type func, @function                  \n\t"
"func:                                  \n\t"
"   movl $7, %eax                       \n\t"
"   ret                                 \n\t"
);

int main(void)
{
    int n = func();
    printf("n = %d\n", n);
    return 0;
}

func.s

	.file	"func.c"
	.text
#APP
	.globl func                            ;内联汇编
	.type func, @function                  
	func:                                  
	   movl $7, %eax                       
	   ret                                 
	
	.section	.rodata
.LC0:
	.string	"n = %d\n"
#NO_APP
	.text
	.globl	main
	.type	main, @function
main:
.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	$0, %eax
	call	func@PLT
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	movl	%eax, %esi
	leaq	.LC0(%rip), %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

4 协程栈的测试

coctx_swap_test.cpp

#include <cstdio>
#include <cstring>
#include <cstdlib>

struct coctx_t
{
    void* rax;      // 0
    void* rbx;      // 8
    void* rcx;      // 16
    void* rdx;      // 24
    void* rsi;      // 32
    void* rdi;      // 40
    void* rbp;      // 48
    void* rsp;      // 56
    void* r8;       // 64
    void* r9;       // 72
    void* r12;      // 80
    void* r13;      // 88
    void* r14;      // 96
    void* r15;      // 104
    void* rip;      // 112

    size_t ss_size; // 120 协程栈大小
    char* ss_sp;    // 128 协程栈指针
};

extern "C"
{
void coctx_swap(coctx_t* /*rdi*/, coctx_t* /*rsi*/) asm("coctx_swap");
}

// %rdi %rsi 第一个和第二个参数
__asm__(
".global coctx_swap                                     \n"
".type coctx_swap, @function                            \n"
"coctx_swap:                                            \n"

"   movq %rax, 0(%rdi)                                  \n"
"   movq %rbx, 8(%rdi)                                  \n"
"   movq %rcx, 16(%rdi)                                 \n"
"   movq %rdx, 24(%rdi)                                 \n"
"   movq %rsi, 32(%rdi)                                 \n"
"   movq %rdi, 40(%rdi)                                 \n"
"   movq %rbp, 48(%rdi)                                 \n"
"   movq %rsp, 56(%rdi)                                 \n"
"   movq %r8, 64(%rdi)                                  \n"
"   movq %r9, 72(%rdi)                                  \n"
"   movq %r12, 80(%rdi)                                 \n"
"   movq %r14, 96(%rdi)                                 \n"
"   movq %r15, 104(%rdi)                                \n"
"   movq (%rsp), %rax                                   \n"
"   movq %rax, 112(%rdi)                                \n"
"   xorq %rax, %rax                                     \n"

"   movq 0(%rsi), %rax                                  \n"
"   movq 8(%rsi), %rbx                                  \n"
"   movq 16(%rsi), %rcx                                 \n"
"   movq 24(%rsi), %rdx                                 \n"
"   movq 40(%rsi), %rdi                                 \n"
"   movq 48(%rsi), %rbp                                 \n"
"   movq 56(%rsi), %rsp                                 \n"
"   movq 64(%rsi), %r8                                  \n"
"   movq 72(%rsi), %r9                                  \n"
"   movq 80(%rsi), %r12                                 \n"
"   movq 88(%rsi), %r13                                 \n"
"   movq 96(%rsi), %r14                                 \n"
"   movq 104(%rsi), %r15                                \n"
"   leaq 8(%rsp), %rsp                                  \n"
"   push 112(%rsi)                                      \n"
"   movq 32(%rsi), %rsi                                 \n"

"   ret                                                 \n"
);

coctx_t ctx_main;
coctx_t ctx;

void func()
{
    printf("call func\n");
    coctx_swap(&ctx, &ctx_main);
}

int main(void)
{
    ctx.ss_size = 1024;
    ctx.ss_sp = (char*)malloc(ctx.ss_size);
    memset(ctx.ss_sp, 0, ctx.ss_size);
    memset(&ctx, 0, 120);
    ctx.rsp = ctx.ss_sp + ctx.ss_size;
    ctx.rbp = ctx.rsp;
    ctx.rip = (void*)func;

    coctx_swap(&ctx_main, &ctx);
    
    free(ctx.ss_sp);
    return 0;
}
$ g++ coctx_swap_test.cpp
$ ./a.out
call func
  1. 当函数a调用函数b时,即call,会先将返回地址压栈,即将call后面的一个指令的地址压栈(%rsp - 8),然后再执行函数b,即PC指向函数b的第一个指令。

  2. 当函数b返回到函数a时,即ret,会先将返回地址出栈,即将call后面的指令地址赋值给PC(%rsp + 8)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值