ucontext族函数的使用及原理分析


ucontext介绍

寄存器介绍

在这里插入图片描述
以上是ucontext使用到的所有寄存器,下面对他们做一些简单的介绍。

  • %rax作为函数返回值使用
  • %rsp栈指针寄存器, 指向栈顶
  • %rdi, %rsi, %rdx, %rcx, %r8, %r9用作函数的参数,从前往后依次对应第1、第2、…第n参数
  • %rbx, %rbp, %r12, %r13, %r14, %r15用作数据存储,遵循被调用这使用规则,调用子函数之前需要先备份,防止被修改。
  • %r10, %r11用作数据存储,遵循调用者使用规则,使用前需要保存原值

ucontext_t结构体

typedef struct ucontext
  {
    unsigned long int uc_flags;
    struct ucontext *uc_link;// 当前上下文执行完了,恢复运行的上下文 
    stack_t uc_stack;// 该上下文中使用的栈
    mcontext_t uc_mcontext;// 保存当前上下文,即各个寄存器的状态
    __sigset_t uc_sigmask;// 保存当前线程的信号屏蔽掩码
    struct _libc_fpstate __fpregs_mem;
  } ucontext_t;
//描述整个上下文
typedef struct
  {
    gregset_t gregs;//用于装载寄存器
    /* Note that fpregs is a pointer.  */
    fpregset_t fpregs;//所有寄存器的类型
    __extension__ unsigned long long __reserved1 [8];
} mcontext_t;

-----------------------------
//所包含的具体上下文信息
struct _libc_fpxreg
{ 
  unsigned short int significand[4];
  unsigned short int exponent;
  unsigned short int padding[3];
};

struct _libc_xmmreg
{ 
  __uint32_t    element[4];
};

struct _libc_fpstate
{ 
  /* 64-bit FXSAVE format.  */
  __uint16_t        cwd;
  __uint16_t        swd;
  __uint16_t        ftw;
  __uint16_t        fop;
  __uint64_t        rip;
  __uint64_t        rdp;
  __uint32_t        mxcsr;
  __uint32_t        mxcr_mask;
  struct _libc_fpxreg   _st[8];
  struct _libc_xmmreg   _xmm[16];
  __uint32_t        padding[24];
};

----------------------
//装载所有寄存器的容器
__extension__ typedef long long int greg_t;

/* Number of general registers.  */
#define NGREG   23

/* Container for all general registers.  */
typedef greg_t gregset_t[NGREG];


getcontext

int getcontext(ucontext_t *ucp);

将当前的寄存器信息保存到变量ucp中。

下面看看汇编代码

/*  int __getcontext (ucontext_t *ucp)

  Saves the machine context in UCP such that when it is activated,
  it appears as if __getcontext() returned again.

  This implementation is intended to be used for *synchronous* context
  switches only.  Therefore, it does not have to save anything
  other than the PRESERVED state.  */


ENTRY(__getcontext)
    /* Save the preserved registers, the registers used for passing
       args, and the return address.  */
    movq    %rbx, oRBX(%rdi)
    movq    %rbp, oRBP(%rdi)
    movq    %r12, oR12(%rdi)
    movq    %r13, oR13(%rdi)
    movq    %r14, oR14(%rdi)
    movq    %r15, oR15(%rdi)

    movq    %rdi, oRDI(%rdi)
    movq    %rsi, oRSI(%rdi)
    movq    %rdx, oRDX(%rdi)
    movq    %rcx, oRCX(%rdi)
    movq    %r8, oR8(%rdi)
    movq    %r9, oR9(%rdi)

    movq    (%rsp), %rcx
    movq    %rcx, oRIP(%rdi)
    leaq    8(%rsp), %rcx       /* Exclude the return address.  */
    movq    %rcx, oRSP(%rdi)

    /* We have separate floating-point register content memory on the
       stack.  We use the __fpregs_mem block in the context.  Set the
       links up correctly.  */

    leaq    oFPREGSMEM(%rdi), %rcx
    movq    %rcx, oFPREGS(%rdi)
    /* Save the floating-point environment.  */
    fnstenv (%rcx)
    fldenv  (%rcx)
    stmxcsr oMXCSR(%rdi)

    /* Save the current signal mask with
       rt_sigprocmask (SIG_BLOCK, NULL, set,_NSIG/8).  */
    leaq    oSIGMASK(%rdi), %rdx
    xorl    %esi,%esi
#if SIG_BLOCK == 0
    xorl    %edi, %edi
#else
    movl    $SIG_BLOCK, %edi
#endif
    movl    $_NSIG8,%r10d
    movl    $__NR_rt_sigprocmask, %eax
    syscall
    cmpq    $-4095, %rax        /* Check %rax for error.  */
    jae SYSCALL_ERROR_LABEL /* Jump to error handler if error.  */

    /* All done, return 0 for success.  */
    xorl    %eax, %eax
    ret
PSEUDO_END(__getcontext)

weak_alias (__getcontext, getcontext)

这段代码主要执行了几个工作,将当前的寄存器数据存入到%rdi也就是第一个参数ucp中,紧接着调整栈顶指针%rsp。然后设置浮点计算器,保存当前线程的信号屏蔽掩码。


setcontext

int setcontext(const ucontext_t *ucp);

将变量ucp中保存的寄存器信息恢复到CPU中。

汇编代码

/*  int __setcontext (const ucontext_t *ucp)

  Restores the machine context in UCP and thereby resumes execution
  in that context.

  This implementation is intended to be used for *synchronous* context
  switches only.  Therefore, it does not have to restore anything
  other than the PRESERVED state.  */

ENTRY(__setcontext)
    /* Save argument since syscall will destroy it.  */
    pushq   %rdi
    cfi_adjust_cfa_offset(8)

    /* Set the signal mask with
       rt_sigprocmask (SIG_SETMASK, mask, NULL, _NSIG/8).  */
    leaq    oSIGMASK(%rdi), %rsi
    xorl    %edx, %edx
    movl    $SIG_SETMASK, %edi
    movl    $_NSIG8,%r10d
    movl    $__NR_rt_sigprocmask, %eax
    syscall
    popq    %rdi            /* Reload %rdi, adjust stack.  */
    cfi_adjust_cfa_offset(-8)
    cmpq    $-4095, %rax        /* Check %rax for error.  */
    jae SYSCALL_ERROR_LABEL /* Jump to error handler if error.  */

    /* Restore the floating-point context.  Not the registers, only the
       rest.  */
    movq    oFPREGS(%rdi), %rcx
    fldenv  (%rcx)
    ldmxcsr oMXCSR(%rdi)


    /* Load the new stack pointer, the preserved registers and
       registers used for passing args.  */
    cfi_def_cfa(%rdi, 0)
    cfi_offset(%rbx,oRBX)
    cfi_offset(%rbp,oRBP)
    cfi_offset(%r12,oR12)
    cfi_offset(%r13,oR13)
    cfi_offset(%r14,oR14)
    cfi_offset(%r15,oR15)
    cfi_offset(%rsp,oRSP)
    cfi_offset(%rip,oRIP)

    movq    oRSP(%rdi), %rsp
    movq    oRBX(%rdi), %rbx
    movq    oRBP(%rdi), %rbp
    movq    oR12(%rdi), %r12
    movq    oR13(%rdi), %r13
    movq    oR14(%rdi), %r14
    movq    oR15(%rdi), %r15

    /* The following ret should return to the address set with
    getcontext.  Therefore push the address on the stack.  */
    movq    oRIP(%rdi), %rcx
    pushq   %rcx

    movq    oRSI(%rdi), %rsi
    movq    oRDX(%rdi), %rdx
    movq    oRCX(%rdi), %rcx
    movq    oR8(%rdi), %r8
    movq    oR9(%rdi), %r9

    /* Setup finally  %rdi.  */
    movq    oRDI(%rdi), %rdi

    /* End FDE here, we fall into another context.  */
    cfi_endproc
    cfi_startproc

    /* Clear rax to indicate success.  */
    xorl    %eax, %eax
    ret
PSEUDO_END(__setcontext)

setcontext的操作与getcontext类似,他将ucp中所保存的上下文信息给取出来,放入当前的寄存器中,使得当前的上下文环境恢复的与ucp一致


makecontext

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

修改上下文信息,设置上下文入口函数func,agrc为参数个数,后面跟着的函数参数必须要是整型值。并且在makecontext之前,需要为上下文设置栈空间ucp->stack以及设置后继上下文ucp->uc_link。

汇编代码

 void
  __makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...)
  {
    extern void __start_context (void);
    greg_t *sp;
    unsigned int idx_uc_link;
    va_list ap; 
    int i;
  
    /* Generate room on stack for parameter if needed and uc_link.  */
    sp = (greg_t *) ((uintptr_t) ucp->uc_stack.ss_sp
             + ucp->uc_stack.ss_size);
    sp -= (argc > 6 ? argc - 6 : 0) + 1;
    /* Align stack and make space for trampoline address.  */
    sp = (greg_t *) ((((uintptr_t) sp) & -16L) - 8); 
  
    idx_uc_link = (argc > 6 ? argc - 6 : 0) + 1;
  
    /* Setup context ucp.  */
    /* Address to jump to.  */
    ucp->uc_mcontext.gregs[REG_RIP] = (uintptr_t) func;
    /* Setup rbx.*/
    ucp->uc_mcontext.gregs[REG_RBX] = (uintptr_t) &sp[idx_uc_link];
    ucp->uc_mcontext.gregs[REG_RSP] = (uintptr_t) sp; 
  
    /* Setup stack.  */
    sp[0] = (uintptr_t) &__start_context;
    sp[idx_uc_link] = (uintptr_t) ucp->uc_link;
  
    va_start (ap, argc);
    /* Handle arguments.
  
       The standard says the parameters must all be int values.  This is
       an historic accident and would be done differently today.  For
       x86-64 all integer values are passed as 64-bit values and
       therefore extending the API to copy 64-bit values instead of
       32-bit ints makes sense.  It does not break existing
       functionality and it does not violate the standard which says
       that passing non-int values means undefined behavior.  */
    for (i = 0; i < argc; ++i)
      switch (i)
        {
        case 0:
      ucp->uc_mcontext.gregs[REG_RDI] = va_arg (ap, greg_t);
      break;
        case 1:
      ucp->uc_mcontext.gregs[REG_RSI] = va_arg (ap, greg_t);
      break;
        case 2:
      ucp->uc_mcontext.gregs[REG_RDX] = va_arg (ap, greg_t);
      break;
        case 3:
      ucp->uc_mcontext.gregs[REG_RCX] = va_arg (ap, greg_t);
      break;
        case 4:
      ucp->uc_mcontext.gregs[REG_R8] = va_arg (ap, greg_t);
      break;
        case 5:
      ucp->uc_mcontext.gregs[REG_R9] = va_arg (ap, greg_t);
      break;
        default:
      /* Put value on stack.  */
      sp[i - 5] = va_arg (ap, greg_t);
      break;
        }
    va_end (ap);
  }

这段代码的主要内容其实就是为用户的自定义栈进行处理,将当前运行栈切换为用户的自定义栈,并且将用户传入的入口函数放入rip中,rbx指向后继上下文,rsp指向栈顶。

ENTRY(__start_context)
    /* This removes the parameters passed to the function given to
       'makecontext' from the stack.  RBX contains the address
       on the stack pointer for the next context.  */
    movq    %rbx, %rsp

    /* Don't use pop here so that stack is aligned to 16 bytes.  */
    movq    (%rsp), %rdi        /* This is the next context.  */
    testq   %rdi, %rdi
    je  2f          /* If it is zero exit.  */

    call    __setcontext
    /* If this returns (which can happen if the syscall fails) we'll
       exit the program with the return error value (-1).  */
    movq    %rax,%rdi

2:
    call    HIDDEN_JUMPTARGET(exit)
    /* The 'exit' call should never return.  In case it does cause
       the process to terminate.  */
    hlt 
END(__start_context)

makecontext通过调用__start_context()来实现后继上下文的功能,其实就是将后继上下文作为setcontext的参数,调用setcontext将当前上下文设置到后继上下文的状态


swapcontext

int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

切换上下文,保存当前上下文到oucp中,然后激活ucp中的上下文

汇编代码

/* int __swapcontext (ucontext_t *oucp, const ucontext_t *ucp);

  Saves the machine context in oucp such that when it is activated,
  it appears as if __swapcontextt() returned again, restores the
  machine context in ucp and thereby resumes execution in that
  context.

  This implementation is intended to be used for *synchronous* context
  switches only.  Therefore, it does not have to save anything
  other than the PRESERVED state.  */

ENTRY(__swapcontext)
    /* Save the preserved registers, the registers used for passing args,
       and the return address.  */
    movq    %rbx, oRBX(%rdi)
    movq    %rbp, oRBP(%rdi)
    movq    %r12, oR12(%rdi)
    movq    %r13, oR13(%rdi)
    movq    %r14, oR14(%rdi)
    movq    %r15, oR15(%rdi)

    movq    %rdi, oRDI(%rdi)
    movq    %rsi, oRSI(%rdi)
    movq    %rdx, oRDX(%rdi)
    movq    %rcx, oRCX(%rdi)
    movq    %r8, oR8(%rdi)
    movq    %r9, oR9(%rdi)

    movq    (%rsp), %rcx
    movq    %rcx, oRIP(%rdi)
    leaq    8(%rsp), %rcx       /* Exclude the return address.  */
    movq    %rcx, oRSP(%rdi)

    /* We have separate floating-point register content memory on the
       stack.  We use the __fpregs_mem block in the context.  Set the
       links up correctly.  */
    leaq    oFPREGSMEM(%rdi), %rcx
    movq    %rcx, oFPREGS(%rdi)
    /* Save the floating-point environment.  */
    fnstenv (%rcx)
    stmxcsr oMXCSR(%rdi)


    /* The syscall destroys some registers, save them.  */
    movq    %rsi, %r12

    /* Save the current signal mask and install the new one with
       rt_sigprocmask (SIG_BLOCK, newset, oldset,_NSIG/8).  */
    leaq    oSIGMASK(%rdi), %rdx
    leaq    oSIGMASK(%rsi), %rsi
    movl    $SIG_SETMASK, %edi
    movl    $_NSIG8,%r10d
    movl    $__NR_rt_sigprocmask, %eax
    syscall
    cmpq    $-4095, %rax        /* Check %rax for error.  */
    jae SYSCALL_ERROR_LABEL /* Jump to error handler if error.  */

    /* Restore destroyed registers.  */
    movq    %r12, %rsi

    /* Restore the floating-point context.  Not the registers, only the
       rest.  */
    movq    oFPREGS(%rsi), %rcx
    fldenv  (%rcx)
    ldmxcsr oMXCSR(%rsi)

    /* Load the new stack pointer and the preserved registers.  */
    movq    oRSP(%rsi), %rsp
    movq    oRBX(%rsi), %rbx
    movq    oRBP(%rsi), %rbp
    movq    oR12(%rsi), %r12
    movq    oR13(%rsi), %r13
    movq    oR14(%rsi), %r14
    movq    oR15(%rsi), %r15

    /* The following ret should return to the address set with
    getcontext.  Therefore push the address on the stack.  */
    movq    oRIP(%rsi), %rcx
    pushq   %rcx

    /* Setup registers used for passing args.  */
    movq    oRDI(%rsi), %rdi
    movq    oRDX(%rsi), %rdx
    movq    oRCX(%rsi), %rcx
    movq    oR8(%rsi), %r8
    movq    oR9(%rsi), %r9

    /* Setup finally  %rsi.  */
    movq    oRSI(%rsi), %rsi

    /* Clear rax to indicate success.  */
    xorl    %eax, %eax
    ret
PSEUDO_END(__swapcontext)

从汇编代码可以看出来,swapcontext的主要操作其实就是整合了getcontext和setcontext。首先将当前的上下文环境保存到ousp中,紧接着将当前的上下文环境设置为usp中的上下文环境。


使用示例

示例一、上下文的保存与恢复(getcontext、setcontext)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>

int main() 
{
	int i = 0;
	ucontext_t ctx;

	getcontext(&ctx);//在该位置保存上下文
	printf("i = %d\n", i++);
	sleep(2);
	setcontext(&ctx);//将上下文恢复至设置时的状态,完成死循环
	
	return 0;
}

通过在第三行的地方设置上下文,每次执行完计数后,将上下文环境恢复至getcontext的位置,实现循环计数。
在这里插入图片描述
在这里插入图片描述


示例二、上下文的修改(makecontext)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>

void fun( void ) {
	printf("fun()\n");
}


int main( void ) {
	int i = 1;
	char *stack = (char*)malloc(sizeof(char)*8192);
	ucontext_t ctx_main, ctx_fun;

	getcontext(&ctx_main);//保存ctx_main上下文
	getcontext(&ctx_fun);//保存ctx_fun上下文
	printf("i=%d\n", i++);
	sleep(1);

	//设置上下文的栈信息
	ctx_fun.uc_stack.ss_sp    = stack;
	ctx_fun.uc_stack.ss_size  = 8192;
	ctx_fun.uc_stack.ss_flags = 0;
	ctx_fun.uc_link = &ctx_main;//设置ctx_main为ctx_fun的后继上下文

	makecontext(&ctx_fun, fun, 0); // 修改上下文信息,设置入口函数与参数
	
	setcontext(&ctx_fun);//恢复ctx_fun上下文
	
	printf("main exit\n");
}

在这里插入图片描述
进入死循环,每次ctx_fun执行完fun函数后就会跳转到后继上下文ctx_main的保存位置的下一句,然后继续开始计数,当走到setcontext的时候再次跳转至fun函数,进入死循环,如下图。
在这里插入图片描述


示例三、上下文的切换(swapcontext)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>

ucontext_t ctx_main, ctx_f1, ctx_f2;

void fun1( void ) {
	printf("fun1() start\n");
	swapcontext(&ctx_f1, &ctx_f2);//切换至f2上下文
	printf("fun1() end\n");
}

void fun2( void ) {
	printf("fun2() start\n");
	swapcontext(&ctx_f2, &ctx_f1);//切换回f1上下文
	printf("fun2() end\n");
}

int main( void ) {
	char stack1[1024*8];
	char stack2[1024*8];
	
	getcontext(&ctx_f1);
	getcontext(&ctx_f2);

	ctx_f1.uc_stack.ss_sp    = stack1;
	ctx_f1.uc_stack.ss_size  = 1024*8;
	ctx_f1.uc_stack.ss_flags = 0;
	ctx_f1.uc_link = &ctx_f2;//f1设置后继上下文为f2
	makecontext(&ctx_f1, fun1, 0);//设置入口函数

	ctx_f2.uc_stack.ss_sp    = stack2;
	ctx_f2.uc_stack.ss_size  = 1024*8;
	ctx_f2.uc_stack.ss_flags = 0;
	ctx_f2.uc_link = &ctx_main;//f2后继上下文为主流程
	makecontext(&ctx_f2, fun2, 0);//设置入口函数

	swapcontext(&ctx_main, &ctx_f1);//保存ctx_main,从主流程上下文切换至ctx_f1上下文

	printf("main exit\n");
}

在这里插入图片描述
首先从主流程ctx_main切换至ctx_fun1的入口函数fun1,执行完fun1 start后切换至ctx_fun2的入口函数fun2。接着执行fun2 start,然后再次切换回ctx_fun1,执行fun1 end,此时fun1上下文执行结束,跳转至后继上下文ctx_fun2,执行fun2 end。接着fun2也执行结束,跳转至后继上下文主流程ctx_main,执行main exit退出。
在这里插入图片描述

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌桓丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值