天天看點

go語言如何使用rbp, rsp, 參數如何傳遞, 為什麼go的傳回值寫在後面為什麼go的傳回值寫在後面

為什麼go的傳回值寫在後面

go一直被鼓吹文法比java好, 性能跟c一樣. 讓我們來看一看go語言各部分對應的二進制指令, 是如何實作的

現在的想法是寫個一系列文章, 把go的所有文法的實作方式都分析一遍, 不知道會不會半途而廢

本文所有的分析方法, 結論都是本人猜測的, 查各種文檔太費時間了, 當然不是亂猜, 都是有依據的

先看棧回溯最基本的方法, rbp, rsp的使用情況, 現在的實驗都是加了-N -l的, 簡單化

rbp, rsp

随便寫了go語言

package main

func boo(a int, b int) int {
    return a + b
}
func aoo(a int, b int) int {
    c := 10
    return a + b + c + boo(1, 2)
}
func main() {
    aoo(1, 2)
}
           
81094 000000000044dfc0 <main.aoo>:
81095   44dfc0:>------64 48 8b 0c 25 f8 ff >--mov    %fs:0xfffffffffffffff8,%rcx
81096   44dfc7:>------ff ff-
81097   44dfc9:>------48 3b 61 10          >--cmp    0x10(%rcx),%rsp
81098   44dfcd:>------76 61                >--jbe    44e030 <main.aoo+0x70>
81099   44dfcf:>------48 83 ec 30          >--sub    $0x30,%rsp
81100   44dfd3:>------48 89 6c 24 28       >--mov    %rbp,0x28(%rsp)
81101   44dfd8:>------48 8d 6c 24 28       >--lea    0x28(%rsp),%rbp
81102   44dfdd:>------48 c7 44 24 48 00 00 >--movq   $0x0,0x48(%rsp)
81103   44dfe4:>------00 00-
81104   44dfe6:>------48 c7 44 24 18 0a 00 >--movq   $0xa,0x18(%rsp)
81105   44dfed:>------00 00-
81106   44dfef:>------48 c7 04 24 01 00 00 >--movq   $0x1,(%rsp)
81107   44dff6:>------00-
81108   44dff7:>------48 c7 44 24 08 02 00 >--movq   $0x2,0x8(%rsp)
81109   44dffe:>------00 00-
81110   44e000:>------e8 9b ff ff ff       >--callq  44dfa0 <main.boo>
81111   44e005:>------48 8b 44 24 10       >--mov    0x10(%rsp),%rax           

拿出上面的rsp rbp部分的代碼

81099   44dfcf:>------48 83 ec 30          >--sub    $0x30,%rsp
81100   44dfd3:>------48 89 6c 24 28       >--mov    %rbp,0x28(%rsp) 
81101   44dfd8:>------48 8d 6c 24 28       >--lea    0x28(%rsp),%rbp
其實就是push rbp; mov rsp rbp; rsp -= 0x28           

一看就很簡單, 先配置設定0x30空間, 儲存之前的rbp到靠近ret ip的地方, 然後把這個位址儲存到rbp寄存器裡面

和c一樣, 就是rbp一直循環指向來做棧回溯, ret ip就是這個棧幀的rbp, + 8 的地方

如果有些函數不需要棧, 就沒有這些東西了

參數如何入

func aoo(a int, b int) int {
    c := 10
    return a + b + c + boo(1, 2)
}           
81106   44dfef:>------48 c7 04 24 01 00 00 >--movq   $0x1,(%rsp)
81107   44dff6:>------00-    
81108   44dff7:>------48 c7 44 24 08 02 00 >--movq   $0x2,0x8(%rsp)
81109   44dffe:>------00 00- 
81110   44e000:>------e8 9b ff ff ff       >--callq  44dfa0 <main.boo>           

和c語言一樣, 從右到左, 隻不過這裡直接mov來寫棧裡面的内容了, 不需要push

從右到左是可變參數的必須要的條件

如何擷取參數

func boo(a int, b int) int {
    return a + b
}           
81079 000000000044dfa0 <main.boo>:
81080   44dfa0:>------48 c7 44 24 18 00 00 >--movq   $0x0,0x18(%rsp)
81081   44dfa7:>------00 00- 
81082   44dfa9:>------48 8b 44 24 08       >--mov    0x8(%rsp),%rax
81083   44dfae:>------48 03 44 24 10       >--add    0x10(%rsp),%rax
81084   44dfb3:>------48 89 44 24 18       >--mov    %rax,0x18(%rsp)           

直接rsp加8, 來擷取, 為什麼多了一個8, 因為callq的時候push了一個ret ip

這裡其實可以看到, 傳回值的處理不是rax了, 而是直接操作父函數的第三個參數

mov %rax,0x18(%rsp), 這個語言設計就比c語言更加簡化了, 可以了解為都是參數, 沒有傳回值這個概念, 是以多傳回值也就很好操作了, 第四個參數, 第五個參數, 直接讀寫對應位址就可以了

這樣就很好了解為什麼go喜歡把傳回值寫在後面

func boo(a int, b int) int {
    return a + b
}           

原來傳回值就是第三個參數

這篇文章标題可以寫成, 為什麼go的傳回值寫在後面

繼續閱讀