Golang在ARM/Linux平台上函數參數的傳遞

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。 一.前言

    作為一名初級的嵌入式軟體開發從業者,工作中大部分項目以C語言實現。使用C語言來編寫代碼,通常我們可以預測到編譯產生的彙編/機器編碼的大致情況,在不同的晶片架構上,有其相應的ABI標準。而近年來逐漸流行起來的Go語言編程,雖然同樣文法上和C語言文法都有較為簡單的特點,也都是編譯型的靜態語言,但我們對它在基本類型——函數參數的傳遞方式就瞭解很少了。另外,Go語言的函數可以有多個傳回值,其底層機制是如何?的,也需要分析探究一下。

    本文將記錄Golang官方編譯器產生的可執行檔在ARM/Linux平台上函數傳遞的分析過程。測試使用的裝置是一部安卓手機,在其中安裝了Bash、Git、Vim、GDB等開源軟體(安裝過程請參考https://pan.baidu.com/s/1i5o6Lwh中的《安裝記錄.docx》),並在C4DROID中找到了可在安卓中執行的GCC編譯器,這樣就可以在手機上編譯Golang編譯器了,其版本為1.4.3。之所以選擇該版本的Golang編譯器,是因為後續版本的Golang編譯器的編譯可能會依賴現有的Golang編譯器,這就會產生一個雞生蛋、蛋生雞的問題了。使用TELNET登入手機後,可以查看到,現在已經可以正常工作了:


二.擷取當前的棧指標

    我們知道Go語言的函數定義的關鍵字為func,其傳回值可以為空白,或者有多個傳回值,於是我們猜測它是以棧來傳遞參數和傳回值的,當然事實確實也是如此,那麼在分析函數在調用瞬間、函數返回瞬間,其參數和傳回值在棧空間上如何分配這個問題之前,我們需要擷取當前函數執行的棧指標。但與C語言不同,Go語言並不支援內聯彙編,那我們就需要使用其它的方法。

    這個方法也比較直接,使用彙編來實現,參照Golang程式碼程式庫的一些彙編格式,我們建立了一個nonsafe包,提供了一個Fetchsp()函數:


    當這個函數被引用,產生的可執行檔中該彙編產生的指令為:


    可以看到,我們將棧指標存放在了棧指標的4位元組位移處,這樣的就隱含了我們已知函數傳回值的方法,後面我們會對其進一步驗證。另外,RET偽指令被編譯為add pc, lr, #0,可見Golang 1.4.3版本的ARM彙編器仍不完善。

三.簡單函數的參數傳遞和傳回值

    有了當前函數的棧指標後,我們就可以直接把函數在調用前後的棧上面的資料使用fmt包中的類printf函數輸出查看了。相應的代碼為:


    直接編譯並執行,可以得到結果:


    雖然如此,我們不能確定Fetchsp()返回的棧針是正確的,這就需要對產生的可執行檔進一步查看,得到在調用Fetchsp()之前的指令地址:


    中高亮的部分是我們感興趣的地方。使用gdb調試,讓它在調用Fetchsp()之前停下來:


    這樣,我們就可以確定,Fetchsp函數在Golang 1.4.3版本的正確性了。通過對執行結果的仔細分析,可以得出其下結論:

    函數在傳遞參數時,在當前函數棧指標4位元組位移處開始傳參,[sp + 0x4]為函數第一個參數(0x7e0 = 2016),[sp + 0x8]為函數第二個參數(0x7d9 = 2009);當函數返回時,返回參數存放在最後一個參數之後的位置,即[sp+ 0xc](0x3ee = 2016 * 2009 / (2016 + 2009) = 1006)。

四.interface{}作為函數參數的傳遞

    Go語言中的函數也支援多個、不確定的參數傳遞,在《Programming In Go》一書的5.2.2.2一節中提到了類型檢測,修改過後的函數如下:


    可以看到,函數classifier()除了第一個參數類型是確定的之外,其他參數都是類型和個數不確定的。在C語言中,也有類似的函數,如printf(const char *, …)等。但是我們知道,諸如printf()之類的函數,可變參數部分基本上都是壓入棧的,而且沒有類型資訊,其類型是根據格式化字串來推測的。而Go語言則支援類型檢測,其實現的機制是怎樣的呢?

    通過對產生的可執行檔分析,可以確定在調用classifier()函數之前,入棧了四個參數或者說,入棧了四個值:


    除了第一個指標函數外,那麼可以說只有三個值用於interface{}的多個參數傳遞了。於是我們猜測這三個值類似於一個結構體,包含了所有的可變參數的類型資訊和其相應的值。經過測試,我們使用下面的代碼將其輸出:


    這一段代碼比較難看,並不是因為寫的很複雜,而是因為使用了很多的“強制類型轉換”。在Go語言中,指標不參與數學運算,只好將其轉化為uintptr類型,再轉化為指標並對其解引用。先來看一下執行的結果:


    從可以看到,上面解構造的解析可變參數的功能可以得到可變參數的資訊,與類型檢測得到的參數值很相近,這就說明了上面的解析是正確的。那麼,可變參數列表的資訊結構是怎樣的呢?

    可以確認,一個可變參數列表在棧上傳遞會有三個值,其中後兩個值很可能是相同的,表示為可變參數的個數(在例子中我們傳入了6個可變參數),而第一個值指向了一段棧空間,其中會有兩倍於可變參數的資料,每兩個為一組,其中第一個值為一個指標或標識,用於確定該參數的類型;第二個值為該參數的指標,同樣的,該指標指向棧空間,此處存放了該參數的值。其中有一個例外是nil,它的類型和指標都是0。為了進一步確認這一點,可以查看最後一個參數,float32(2016),使用MATLAB可以驗證其十六進位的表示為0x44fc0000:


五.總結

    瞭解Golang語言編譯出的可執行檔的函數傳參機制,可能對學習Go語言的協助並不大。另外,這些函數傳參的機制在GCCGO編譯產生的可執行檔中並不存在。儘管如此,瞭解一些Golang底層的實現細節,仍然是十分有趣的,也可以略微滿足一下我們對Golang的"ABI"探究的心理。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.