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