亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

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

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

前言

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

編譯源代碼

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

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

 

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

#go build -gcflags '-l -N'

 

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

 

通過(guò)如下命令得到反匯編信息

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

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

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

 

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

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

 

下面開(kāi)始具體分析.

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

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

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

第二部分如下.如果對(duì)C編譯好的代碼進(jìn)行反匯編也能看到基本完全相同的匯編代碼.這部分代碼是對(duì)callee進(jìn)行棧空間的分配和回收的.進(jìn)入一個(gè)callee的時(shí)候,(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,這個(gè)就是main.main的棧幀大小(不同的函數(shù)需要的棧幀大小不一樣,所以這兒的值不同,但是在編譯的時(shí)候是可以計(jì)算出每個(gè)函數(shù)的局部變量需要的大小,以及調(diào)用其他函數(shù)需要傳參使用的大小的),main.main的局部變量就放在這里面的。注意這兒把main.main棧幀的頂部,也就是(0X40)SP放了老的BP的值,然后把這個(gè)地址放到了BP里面。

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

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

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

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

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

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

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

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

 

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

也就是0x45daf2執(zhí)行之后的寄存器和棧的情況. 這個(gè)時(shí)候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ì)算邏輯代碼,然后是根據(jù)計(jì)算的結(jié)果設(shè)置返回值.最后銷(xiāo)毀棧幀并返回.

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

 

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

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

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

 

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

golang的一個(gè)函數(shù)返回值默認(rèn)值需要為0,因?yàn)槲覀兛梢灾苯邮褂貌粠?shù)的return語(yǔ)句.這兒將返回值設(shè)置為0值.

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

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

 

5.銷(xiāo)毀棧幀,返回main.main

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

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

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

 

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

網(wǎng)友整理

注冊(cè)時(shí)間:

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

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

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

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

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

答題星2018-06-03

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

全階人生考試2018-06-03

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

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

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

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

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

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定