成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

make_fcountext、jump_fcontext

silencezwm / 2630人閱讀

摘要:所以子程序調(diào)用是通過棧實(shí)現(xiàn)的,子程序調(diào)用總是一個(gè)入口,一次返回,調(diào)用順序是明確的。棧保存的數(shù)據(jù),指令通過寄存器控制。所以需要多個(gè)棧來維護(hù)。由于棧是從高到底,此處的意思表示預(yù)留字節(jié)的??臻g。

我們先弄清如何進(jìn)行協(xié)程的切換,程序可以在某個(gè)地方掛起,跳轉(zhuǎn)到另外的流程中執(zhí)行,并且可以重新在掛起處繼續(xù)運(yùn)行。那如何實(shí)現(xiàn)呢?

我們先來看一個(gè)例子,有下面2個(gè)函數(shù),如果在一個(gè)單線程中讓輸出結(jié)果依次是 funcA1 funcB1 funcA2 funcB2 ... ,你會(huì)怎么做呢?

void funcA(){
    int i = 0;
    while(true){
        //to do something
        
        printf("funcA%d ",i);
        i++;
    }
}

void funcB(){
    int i = 0;
    while(true){
        //to do something
        
        printf("funcB%d ",i);
        i++;
    }
}

如果從c代碼的角度來看,如果單線程運(yùn)行到func1 的 while循環(huán)中,如何能調(diào)用到func2的while循環(huán)呢?必須使用跳轉(zhuǎn)。

首先想到是goto。goto是可以實(shí)現(xiàn)跳轉(zhuǎn),但是goto不能實(shí)現(xiàn)函數(shù)間的跳轉(zhuǎn)。無法滿足這個(gè)要求。即使可以實(shí)現(xiàn)函數(shù)間跳轉(zhuǎn),難道就可行嗎?

那這里不得不說下C函數(shù)調(diào)用過程

具體相見這篇文章https://blog.csdn.net/jelly_9/article/details/53239718
子程序或者稱為函數(shù),在所有語言中都是層級(jí)調(diào)用,比如A調(diào)用B,B在執(zhí)行過程中又調(diào)用了C,
C執(zhí)行完畢返回,B執(zhí)行完畢返回,最后是A執(zhí)行完畢。
所以子程序調(diào)用是通過棧實(shí)現(xiàn)的,子程序調(diào)用總是一個(gè)入口,一次返回,調(diào)用順序是明確的。

程序運(yùn)行有2個(gè)部分,指令,數(shù)據(jù)。棧保存的數(shù)據(jù),指令通過寄存器(rip)控制。

2個(gè)函數(shù)內(nèi)部的跳轉(zhuǎn)必須保證棧是正確,所以跳轉(zhuǎn)之前需要保存好當(dāng)前的棧信息,然后跳轉(zhuǎn)。
另外我們可以得到另一個(gè)信息,在一個(gè)棧上實(shí)現(xiàn)多個(gè)流程直接的跳轉(zhuǎn)是不能實(shí)現(xiàn)的。
所以需要多個(gè)棧來維護(hù)。

那我們來看jump_fcontext是怎么實(shí)現(xiàn)跳轉(zhuǎn)

c語言函數(shù)聲明
int jump_fcontext(fcontext_t *ofc, fcontext_t nfc, void* vp, bool preserve_fpu);


匯編代碼如下
.text
.globl jump_fcontext
.type jump_fcontext,@function
.align 16
jump_fcontext:
    pushq  %rbp  /* save RBP */
    pushq  %rbx  /* save RBX */
    pushq  %r15  /* save R15 */
    pushq  %r14  /* save R14 */
    pushq  %r13  /* save R13 */
    pushq  %r12  /* save R12 */

    /* prepare stack for FPU */
    leaq  -0x8(%rsp), %rsp

    /* test for flag preserve_fpu */
    cmp  $0, %rcx
    je  1f

    /* save MMX control- and status-word */
    stmxcsr  (%rsp)
    /* save x87 control-word */
    fnstcw   0x4(%rsp)

1:
    /* store RSP (pointing to context-data) in RDI */
    movq  %rsp, (%rdi)

    /* restore RSP (pointing to context-data) from RSI */
    movq  %rsi, %rsp

    /* test for flag preserve_fpu */
    cmp  $0, %rcx
    je  2f

    /* restore MMX control- and status-word */
    ldmxcsr  (%rsp)
    /* restore x87 control-word */
    fldcw  0x4(%rsp)

2:
    /* prepare stack for FPU */
    leaq  0x8(%rsp), %rsp

    popq  %r12  /* restrore R12 */
    popq  %r13  /* restrore R13 */
    popq  %r14  /* restrore R14 */
    popq  %r15  /* restrore R15 */
    popq  %rbx  /* restrore RBX */
    popq  %rbp  /* restrore RBP */

    /* restore return-address */
    popq  %r8

    /* use third arg as return-value after jump */
    movq  %rdx, %rax
    /* use third arg as first arg in context function */
    movq  %rdx, %rdi

    /* indirect jump to context */
    jmp  *%r8
.size jump_fcontext,.-jump_fcontext

/* Mark that we don"t need executable stack.  */
.section .note.GNU-stack,"",%progbits

寄存器的用途可以先了解下https://www.jianshu.com/p/571...

1、保存寄存器

pushq  %rbp  /* save RBP */
pushq  %rbx  /* save RBX */
pushq  %r15  /* save R15 */
pushq  %r14  /* save R14 */
pushq  %r13  /* save R13 */
pushq  %r12  /* save R12 */

“被調(diào)函數(shù)有義務(wù)保證 rbp rbx r12~r15 這幾個(gè)寄存器的值在進(jìn)出函數(shù)前后一致”
rbx 是基址寄存器 作用存放存儲(chǔ)區(qū)的起始地址 被調(diào)用者保存
rbp (base pointer)基址指針寄存器,用于提供堆棧內(nèi)某個(gè)單元的偏移地址,與rss段寄存器聯(lián)用, 
可以訪問堆棧中的任一個(gè)存儲(chǔ)單元,被調(diào)用者保存

2、預(yù)留fpu 8個(gè)字節(jié)空間

/* prepare stack for FPU */
leaq  -0x8(%rsp), %rsp
表示 %rsp中的內(nèi)容減8。由于棧是從高到底,此處的意思表示預(yù)留8字節(jié)的??臻g。
FPU:(Float Point Unit,浮點(diǎn)運(yùn)算單元)

3、判斷是否保存fpu

cmp  $0, %rcx
je  1f

rcx是第四個(gè)參數(shù),判斷是否等于0。如果為0,跳轉(zhuǎn)到1標(biāo)示的位置。也就是preserve_fpu 。

當(dāng)preserve_fpu = true 的時(shí)候,需要執(zhí)行2個(gè)指令是將浮點(diǎn)型運(yùn)算的2個(gè)32位寄存器數(shù)據(jù)保存到第2步中預(yù)留的8字節(jié)空間。
/* save MMX control- and status-word */
stmxcsr  (%rsp)
/* save x87 control-word */
fnstcw   0x4(%rsp)

4、修改rsp 此時(shí)已經(jīng)改變到其他棧
將rsp 保存到第一參數(shù)(第一個(gè)參數(shù)保存在rdi)指向的內(nèi)存。fcontext_t *ofc 第一參數(shù)ofc指向的內(nèi)存中保存是 rsp 的指針。第二條指令,實(shí)現(xiàn)了將第二個(gè)參數(shù)復(fù)制到 rsp.

1:
/* store RSP (pointing to context-data) in RDI */
movq  %rsp, (%rdi)

/* restore RSP (pointing to context-data) from RSI */
movq  %rsi, %rsp

5、判斷是否保存了fpu,如果保存了就恢復(fù)保存在nfx 棧上的 fpu相關(guān)數(shù)據(jù)到響應(yīng)的寄存器。

/* test for flag preserve_fpu */
cmp  $0, %rcx
je  2f

/* restore MMX control- and status-word */
ldmxcsr  (%rsp)
/* restore x87 control-word */
fldcw  0x4(%rsp)

6、將rsp 存儲(chǔ)的地址+8(8字節(jié)fpu),按順序?qū)V袛?shù)據(jù)恢復(fù)到寄存器中。

2:
/* prepare stack for FPU */
leaq  0x8(%rsp), %rsp

popq  %r12  /* restrore R12 */
popq  %r13  /* restrore R13 */
popq  %r14  /* restrore R14 */
popq  %r15  /* restrore R15 */
popq  %rbx  /* restrore RBX */
popq  %rbp  /* restrore RBP */

7、設(shè)置返回值,實(shí)現(xiàn)指令跳轉(zhuǎn)。
接下來繼續(xù)pop 數(shù)據(jù),那棧上存的是什么呢,在c函數(shù)調(diào)用文章中可以知道,call的時(shí)候會(huì)保存rip(指令寄存器)到棧。所以此時(shí)POP的數(shù)據(jù)是rip 也就是下一條指令。這是下一條指令是nfx 棧保存的,所以這是另一個(gè)協(xié)程的下一條指令。保存到 r8。最后跳轉(zhuǎn)下一條指令就恢復(fù)到另一個(gè)協(xié)程運(yùn)行 jmp *%r8。

movq %rdx, %rax 是將上一個(gè)協(xié)程A jump_fcontext第三個(gè)參數(shù)作為當(dāng)前協(xié)程B jump_fcontext 的返回值,可以實(shí)現(xiàn)2個(gè)協(xié)程直接的數(shù)據(jù)傳遞。

movq %rdx, %rdi 如果跳轉(zhuǎn)過去的新的協(xié)程,將第三個(gè)參數(shù)作為協(xié)程B 啟動(dòng)入口void func(int param)的第一參數(shù)。

/* restore return-address */
popq  %r8

/* use third arg as return-value after jump */
movq  %rdx, %rax
/* use third arg as first arg in context function */
movq  %rdx, %rdi

/* indirect jump to context */
jmp  *%r8


了解了程序是如何跳轉(zhuǎn)后,我門在看下如何創(chuàng)建一個(gè)協(xié)程棧呢。make_fcontext
c語言函數(shù)聲明 fcontext_t make_fcontext(void sp, size_t size, void (fn)(int));

.text
.globl make_fcontext
.type make_fcontext,@function
.align 16
make_fcontext:
    /* first arg of make_fcontext() == top of context-stack */
    movq  %rdi, %rax

    /* shift address in RAX to lower 16 byte boundary */
    andq  $-16, %rax

    /* reserve space for context-data on context-stack */
    /* size for fc_mxcsr .. RIP + return-address for context-function */
    /* on context-function entry: (RSP -0x8) % 16 == 0 */
    leaq  -0x48(%rax), %rax

    /* third arg of make_fcontext() == address of context-function */
    movq  %rdx, 0x38(%rax)

    /* save MMX control- and status-word */
    stmxcsr  (%rax)
    /* save x87 control-word */
    fnstcw   0x4(%rax)

    /* compute abs address of label finish */
    leaq  finish(%rip), %rcx
    /* save address of finish as return-address for context-function */
    /* will be entered after context-function returns */
    movq  %rcx, 0x40(%rax)

    ret /* return pointer to context-data */

finish:
    /* exit code is zero */
    xorq  %rdi, %rdi
    /* exit application */
    call  _exit@PLT
    hlt
.size make_fcontext,.-make_fcontext

/* Mark that we don"t need executable stack. */
.section .note.GNU-stack,"",%progbits

1、第一個(gè)參數(shù)是程序申請(qǐng)的內(nèi)存地址高位(棧是從高到低),將第一個(gè)參數(shù)放到rax,將地址取16的整數(shù)倍。
andq $-16, %rax 表示低4位取0。 -16 的補(bǔ)碼表示為0xfffffffff0.

/* first arg of make_fcontext() == top of context-stack */
movq  %rdi, %rax

/* shift address in RAX to lower 16 byte boundary */
andq  $-16, %rax

2、預(yù)留72字節(jié)棧空間,將第3個(gè)參數(shù)(void (*fn)(int)函數(shù)指針)保存在當(dāng)前偏移0x38位置(大小8字節(jié))。

/* reserve space for context-data on context-stack */
/* size for fc_mxcsr .. RIP + return-address for context-function */
/* on context-function entry: (RSP -0x8) % 16 == 0 */
leaq  -0x48(%rax), %rax

/* third arg of make_fcontext() == address of context-function */
movq  %rdx, 0x38(%rax)

3、保存fpu 和jump_fcontext 類似總大小8字節(jié)。

/* save MMX control- and status-word */
stmxcsr  (%rax)
/* save x87 control-word */
fnstcw   0x4(%rax)

4、計(jì)算finish的絕對(duì)地址,保存到棧的0x40位置。
leaq finish(%rip), %rcx 表示finish是相對(duì)位置+rip 就是finish的函數(shù)的地址。

/* compute abs address of label finish */
leaq  finish(%rip), %rcx
/* save address of finish as return-address for context-function */
/* will be entered after context-function returns */
movq  %rcx, 0x40(%rax)

5、返回,rax 作為返回值,目前的指向可以當(dāng)做新棧的棧頂,相當(dāng)于rsp

ret /* return pointer to context-data */

我們回頭在看看為什么會(huì)預(yù)留72字節(jié)大小。首先知道jump_fcontext 在新棧需要 pop 的大小為,fpu(8字節(jié))+ rbp rbx r12 ~ r15 (8*6 = 48 字節(jié)) = 56 字節(jié)。還會(huì)繼續(xù)POP rip 8 字節(jié),所以可以看到第二步中 movq %rdx, 0x38(%rax),就是將rip 保存到這個(gè)位置。
目前已經(jīng)64字節(jié)了,棧還有存儲(chǔ)什么呢,協(xié)程(fn 函數(shù))運(yùn)行完成后會(huì)退出調(diào)用ret,其實(shí)就是POP到 rip.所以保存是finish 函數(shù)指針 大小8字節(jié)??偣?72 字節(jié)。

make_fcontext 創(chuàng)建協(xié)程的棧。jump_fcontext實(shí)現(xiàn)跳轉(zhuǎn)。

網(wǎng)校內(nèi)部培訓(xùn)視頻by李樂 https://biglive.xueersi.com/L...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/31450.html

相關(guān)文章

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<