日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言

不管是C語言還是golang語言,都有自己的函數(shù)調(diào)用流程,主要是在函數(shù)調(diào)用過程中,各種寄存器和內(nèi)存堆棧的變化. 理解清楚整個函數(shù)調(diào)用流程,可以加深對golang語言的了解.

編譯源代碼

對下面的簡單函數(shù),通過反匯編和調(diào)試器來看下golang的函數(shù)調(diào)用流程,主要是函數(shù)調(diào)用過程中的參數(shù)傳遞和關(guān)鍵寄存器的變化。

golang函數(shù)調(diào)用流程詳解

 

為了避免編譯器的優(yōu)化,加上-gcflags '-l -N'選項,-gcflags是給編譯器的選項,通過go tool compile可以看到選項列表,-l表示禁止內(nèi)聯(lián),-N表示禁止優(yōu)化。一般我們要看一些細(xì)節(jié)的時候,都需要把這兩個選項帶上。

#go build -gcflags '-l -N'

 

golang函數(shù)調(diào)用流程詳解

 

通過如下命令得到反匯編信息

#go tool objdump --gnu -S 0913 > tmp.s

打開tmp.s文件,找到main.test2,能看到main.main和main.test2的反匯編信息,這兒把plan9會把和gnu匯編都顯示。

golang函數(shù)調(diào)用流程詳解

 

整個調(diào)用流程如下

golang函數(shù)調(diào)用流程詳解

 

下面開始具體分析.

前導(dǎo)和結(jié)尾

分析main.main,發(fā)現(xiàn)golang編譯器給函數(shù)固定插入的前導(dǎo)和結(jié)尾有兩部分.

第一部分如下.其作用是保證當(dāng)前goroutine的棧空間足夠,其方法是通過得到當(dāng)前棧空間接近底部的一個地址0x10(CX)(g.stack.stackguard0)并和當(dāng)前SP比較,如果SP的值小于等于0X10(CX)的值,那么棧的空間已經(jīng)馬上不夠用了,必須進(jìn)行擴容,然后就會jmp到runtime.morestack_noctxt進(jìn)行擴容,完成之后再JMP到main.main,如果還是不夠,就再擴容,直到檢查到夠了。 因為golang中的goroutine使用的棧都是新建的,初始值默認(rèn)為2K,隨著函數(shù)調(diào)用層數(shù)增加,或者有些函數(shù)的局部變量占用空間過大,會導(dǎo)致不夠用,這個時候就需要擴容了. 由于這個處理擴容的代碼是golang編譯器加入的,我們就不用關(guān)心了.

0x45dac0              64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX               // mov %fs:0xfffffff8,%rcx
0x45dac9              483b6110                CMPQ 0x10(CX), SP                    // cmp 0x10(%rcx),%rsp
0x45dacd              0f8687000000            JBE 0x45db5a                         // jbe 0x45db5a
。。。
0x45db5a              e821aeffff              CALL runtime.morestack_noctxt(SB)    // callq 0x458980
0x45db5f              90                      NOPL                                 // nop
0x45db60              e95bffffff              JMP main.main(SB)                    // jmpq 0x45dac0

第二部分如下.如果對C編譯好的代碼進(jìn)行反匯編也能看到基本完全相同的匯編代碼.這部分代碼是對callee進(jìn)行棧空間的分配和回收的.進(jìn)入一個callee的時候,(0)SP是返回地址,也就是callee執(zhí)行完成之后,caller要執(zhí)行的指令地址。

0x45dad3 4883ec48 SUBQ $0x48, SP // sub $0x48,%rsp
0x45dad7 48896c2440 MOVQ BP, 0x40(SP) // mov %rbp,0x40(%rsp)
0x45dadc 488d6c2440 LEAQ 0x40(SP), BP // lea 0x40(%rsp),%rbp
。。。
0x45db50 488b6c2440 MOVQ 0x40(SP), BP // mov 0x40(%rsp),%rbp
0x45db55 4883c448 ADDQ $0x48, SP // add $0x48,%rsp
0x45db59 c3 RET // retq

先將SP的地址下移0x48,這個就是main.main的棧幀大小(不同的函數(shù)需要的棧幀大小不一樣,所以這兒的值不同,但是在編譯的時候是可以計算出每個函數(shù)的局部變量需要的大小,以及調(diào)用其他函數(shù)需要傳參使用的大小的),main.main的局部變量就放在這里面的。注意這兒把main.main棧幀的頂部,也就是(0X40)SP放了老的BP的值,然后把這個地址放到了BP里面。

這樣就BP的值是一個地址,這個地址里面存放著上一個棧幀的BP的值,其也是一個地址,這樣BP的值就弄成了一個鏈表,可以不斷向上串聯(lián)起來。當(dāng)然,如果我們不關(guān)心這個事情,那么BP是不需要的,實際上現(xiàn)在gcc有個選項就可以關(guān)閉對BP寄存器的使用,golang編譯器在優(yōu)化的情況下也會不使用BP寄存器。棧幀里面所有的變量通過SP寄存器進(jìn)行偏移就可以訪問到了。我們看上面的匯編代碼,確實都沒有用到BP寄存器來定位變量。

在函數(shù)調(diào)用完成的時候,通過上面相反的調(diào)用順序?qū)?臻g進(jìn)行回收.

詳細(xì)調(diào)用過程分析

上面已經(jīng)介紹了基本過程.下面再通過分析main.main調(diào)用main.test2的整個過程來加深理解,推薦通過dlv工具來自己一步一步的走,可以加深理解.

進(jìn)入代碼目錄使用dlv debug命令開始調(diào)試,然后使用b main.main設(shè)置斷點,c開始運行,使用disassembly看反匯編代碼,使用si命令來單指令執(zhí)行.

1.main.main CALL main.test2之前的棧幀

也就是上面的0x45daf2執(zhí)行之前的寄存器和棧的情況.現(xiàn)在RBP和RSP是main.main的棧幀,然后main.test2需要的兩個入?yún)⒁呀?jīng)準(zhǔn)備好了.

golang函數(shù)調(diào)用流程詳解

 

2.main.main CALL main.test2之后的棧幀

也就是0x45daf2執(zhí)行之后的寄存器和棧的情況. 這個時候SP的值-=8,里面存放main.test2執(zhí)行完成之后返回main.main的執(zhí)行指令的地址,也就是CALL下面那條指令的地址.由于RIP的值被CALL指令修改了,CPU執(zhí)行的下一條指令就是main.test2的第一條指令了.

golang函數(shù)調(diào)用流程詳解

 

main.test2(以及其他函數(shù))本身的執(zhí)行流程如下.

首先是棧幀的準(zhǔn)備,然后是返回值的初始值設(shè)置.然后是核心的計算邏輯代碼,然后是根據(jù)計算的結(jié)果設(shè)置返回值.最后銷毀棧幀并返回.

golang函數(shù)調(diào)用流程詳解

 

3.main.test2準(zhǔn)備棧幀

通過將RSP的值調(diào)整小(棧幀向下生長),擴展好main.test2函數(shù)的棧幀,然后設(shè)置和RBP的值來標(biāo)記棧幀的開始,同時讓各個棧幀的RBP本身可以形成一個鏈表,方便調(diào)試器查找.

golang函數(shù)調(diào)用流程詳解

 

4.初始化返回值,完成計算邏輯,保存返回值.

golang的一個函數(shù)返回值默認(rèn)值需要為0,因為我們可以直接使用不帶參數(shù)的return語句.這兒將返回值設(shè)置為0值.

在通過相應(yīng)的指令完成計算邏輯之后,把返回值保持到main.main的棧幀里面,注意這整個過程中RSP和RBP都不會變化的,局部變量,入?yún)?出參都是通過對RSP進(jìn)行偏移得到的(入?yún)?出參會偏移到main.main的棧幀里面,局部變量偏移到自己的棧幀里面),具體的偏移值編譯器在編譯過程中是可以計算出來的. 注意全f的表示值-1.

golang函數(shù)調(diào)用流程詳解

 

5.銷毀棧幀,返回main.main

在恢復(fù)了BP和SP之后,SP處值為main.main中CALL語句壓入的下一條語句的地址.

main.test2的最后一條RET語句執(zhí)行完成之后,RIP的值值變成SP處的值,SP+=8.棧幀完全恢復(fù)到CALL語句之前,唯一變的是main.main棧幀中返回值的兩個地址里面變成了經(jīng)過main.test2執(zhí)行的值.然后SP-0X28這部分的內(nèi)存就無效了,雖然里面的內(nèi)容并沒有被清空為全0.

golang函數(shù)調(diào)用流程詳解

 

分享到:
標(biāo)簽:函數(shù) golang
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定