了解Go第一步:Go与Plan 9汇编语言
生活随笔
收集整理的這篇文章主要介紹了
了解Go第一步:Go与Plan 9汇编语言
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
本文快速閱讀需要一定的匯編、Go、編譯原理基礎(chǔ)
因水平極其有限,錯誤難以避免,歡迎批評指正
1. Go與Plan 9
- 一圖勝千言:
- 網(wǎng)傳,開發(fā)Go的一些重要人物也是Plan 9項目的重要人物,所以Go匯編和一些工具鏈?zhǔn)荘lan 9項目搬過來的。因為這個匯編獨立與所有的CPU架構(gòu)和操作系統(tǒng)(獨立于操作系統(tǒng),其實生成的匯編已經(jīng)要使用寄存器了,每個架構(gòu)寄存器情況不同)。所以Go項目需要為具體架構(gòu)和操作系統(tǒng)生成目標(biāo)機(jī)器代碼。所以我們甚至可以把Go匯編理解成Go的一種IR。
- Go匯編學(xué)習(xí)資料:
- 官網(wǎng)
- 《Go語言高級編程》第三章
- 網(wǎng)上大部分書籍和資料的匯編停留在1.17以前的版本,但是1.17開始(最新的1.18支持更多架構(gòu))函數(shù)調(diào)用有了新ABI規(guī)范。所以如果我們的Go版本比較新,那么可能生成的匯編和網(wǎng)上各種教程里的不太一樣。其實也沒有關(guān)系,沒有太大區(qū)別。本文的匯編是基于Go1.17生成的。
2. 一段相對簡單的Go代碼學(xué)習(xí)Go匯編
- 前置知識:簡單強(qiáng)調(diào)一下本文閱讀預(yù)備知識中的一些知識點
- 編譯原理:一個程序編譯的過程為詞法分析,語法分析,語義分析,中間代碼生成,代碼分析和優(yōu)化,目標(biāo)代碼生成。對于其它語言的編譯器后端,生成的目標(biāo)代碼一般就是對應(yīng)平臺的匯編代碼。再由對應(yīng)匯編器處理。而對于Go,可以認(rèn)為生成的目標(biāo)代碼在任何時候都是Plan 9匯編(屏蔽了操作系統(tǒng)帶來的差異,如系統(tǒng)調(diào)用規(guī)范,而CPU帶給Go匯編的主要差異就是寄存器數(shù)量和名字)。之后會再根據(jù)架構(gòu)和操作系統(tǒng)翻譯成對應(yīng)的機(jī)器代碼,所以也有人稱Go在這個層面是平臺無關(guān)性的。
- 匯編基礎(chǔ):這里說一下調(diào)用約定,我們程序員一般研究的對象是Linux/x86-64,其調(diào)用約定為函數(shù)參數(shù)只有6個能放在寄存器中,多于6個需要放入棧中。返回地址也在寄存器中。而Go1.17之前,Go調(diào)用約定是返回值和調(diào)用參數(shù)都存放在棧中。現(xiàn)在最新版本的函數(shù)調(diào)用參數(shù)是使用寄存器的,帶來了性能的提升。
再說一下程序運行時候的內(nèi)存布局,棧內(nèi)存在內(nèi)存中是由高地址向低地址延伸的,所以每個棧幀的棧低地址大于棧頂。
- Go匯編與主流匯編較大區(qū)別介紹:
- 4個偽寄存器:PC、FP、SP、SB。我們需要重點關(guān)注的是FP與SP。特別是SP也是部分架構(gòu)中的實寄存器。以下內(nèi)容如無特別表述,SP即表示偽SP。
- FP:可以認(rèn)為是當(dāng)前棧幀的棧底(不包括參數(shù)返回值),當(dāng)有寄存器放不下的調(diào)用參數(shù)或者有返回值時。這些對象的尋址會用到FP,且為正偏移(參數(shù)在FP高地址方向存儲)。
- SP:一定要注意區(qū)分真?zhèn)蜸P寄存器。偽SP也可以認(rèn)為是棧底(不包括參數(shù)返回值),而真SP認(rèn)為是棧頂。一般局部變量的尋址會使用偽SP。且為負(fù)偏移。偽寄存器一般需要一個標(biāo)識符和偏移量為前綴,如果沒有標(biāo)識符前綴則是真寄存器。比如(SP)、+8(SP)沒有標(biāo)識符前綴為真SP寄存器,而a(SP)、b+8(SP)有標(biāo)識符為前綴表示偽寄存器。
- 一般一個函數(shù)的棧幀可以認(rèn)為是真?zhèn)蜸P所指地址中間部分。上面的表述中,可能有人認(rèn)為FP和SP一定是在一起的,但是由于返回地址等內(nèi)存需求和內(nèi)存對齊等原因,不是一起的。
- Go匯編的調(diào)用約定中,所有信息都是由調(diào)用者保護(hù)的,所以可以看出,每個函數(shù)棧幀中包含了調(diào)用別的函數(shù)的參數(shù)和返回值空間。
- 4個偽寄存器:PC、FP、SP、SB。我們需要重點關(guān)注的是FP與SP。特別是SP也是部分架構(gòu)中的實寄存器。以下內(nèi)容如無特別表述,SP即表示偽SP。
3. Go匯編閱讀
- 閱讀Go匯編常用的命令為go tool compile -N -l -S 。-N代表不優(yōu)化,不然Go匯編和我們想象的可能大不一樣,-l為不內(nèi)聯(lián),-S為打印匯編信息。還有其它命令也可以使用。在線網(wǎng)站gossa可以實時查看某個函數(shù)的匯編代碼
- 源代碼:
- Go匯編及解讀:每行#開頭的代碼解釋下一行匯編含義
-
函數(shù)定義:TEXT 函數(shù)名(SB), [flags,] $棧大小[-參數(shù)及返回值大小]。再次注意,函數(shù)自己的參數(shù)及返回值不在自己的棧幀中。而自己棧幀大小包括調(diào)用別的函數(shù)的返回值及參數(shù)。flags一般很多,遇到時搜索一下啥意思
-
FUNCDATA和PCDATA:記錄了函數(shù)中指針信息和調(diào)用信息等,panic時的調(diào)用情況及垃圾回收時的根對象都分別依賴它們。它們是編譯器自行插入的,閱讀時可以跳過
-
使用go tool compile -S / go tool objdump命令輸出的匯編來說,所有的 SP 都是真SP即SP寄存器中的地址。所以從下面匯編(使用go tool compile -S -N -l)可以看出沒有負(fù)索引取值
-
a+24(SP)和40(SP):前者代表a的起始地址在SP上方24字節(jié)位置。后者代表的地址為SP上方40字節(jié)處。
-
- 可能你看了上面的匯編有疑問,不是說1.17開始一些架構(gòu)ABI改變了嗎。為什么還是有寄存器和棧空間中的來回復(fù)制。因為上面是加了不優(yōu)化參數(shù)的匯編。當(dāng)我們?nèi)サ?N。就可以看到。sum的棧幀占用內(nèi)存為0。main棧幀空間也大大縮小(連局部變量a , b都不占用空間了)
- 個人覺得如果看上面的Go匯編沒什么阻礙,Go匯編就可以先學(xué)到這了,當(dāng)我們真要到匯編層面找Bug或提升性能時。看不懂再邊學(xué)邊做就行。上來就學(xué)習(xí)完Go匯編所有細(xì)節(jié),這個付出回報比相對于一般人來說是有點低的
4. 最后我來繪制一下上面匯編代碼中棧內(nèi)存的情況
------ celler BP (8 bytes) ------ main函數(shù)棧幀 BP sum.ret (8 bytes) ------ main.a (8 bytes) ------ main.b (8 bytes) ------ sum.b (8 bytes) ------ sum.a (8 bytes) ------ main函數(shù)棧幀 SP ret addr (8 bytes) ------ caller(main) BP (8 bytes) ------ sum函數(shù)棧幀 BP 臨時變量 (8 bytes) ------ sum函數(shù)棧幀 SP總結(jié)
以上是生活随笔為你收集整理的了解Go第一步:Go与Plan 9汇编语言的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 欧盟无线设备指令RED2014/53/E
- 下一篇: 2.3 深度学习开发任务实例