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