摘要:所以子程序調(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
閱讀 1130·2021-11-25 09:43
閱讀 1649·2021-09-13 10:25
閱讀 2613·2021-09-09 11:38
閱讀 3417·2021-09-07 10:14
閱讀 1728·2019-08-30 15:52
閱讀 651·2019-08-30 15:44
閱讀 3588·2019-08-29 13:23
閱讀 1986·2019-08-26 13:33