Go的彙編器快速指南

本文檔簡要介紹了gcGo編譯器使用的很是規形式的彙編語言。該文件不全面。html

彙編程序基於Plan 9彙編程序的輸入樣式,在其餘地方詳細介紹了該樣式 。若是您打算編寫彙編語言,則儘管其中大部分是特定於Plan 9的,但您仍應閱讀該文檔。當前文檔提供了語法摘要以及與該文檔中所解釋內容的區別,並描述了編寫彙編代碼以與Go交互時適用的特性。linux

關於Go的彙編器,最重要的事情是它不是底層機器的直接表示。一些細節正好映射到機器,但有些則否則。這是由於編譯器套件(請參見此 描述)不須要在常規管道中傳遞任何彙編程序。取而代之的是,編譯器對一種半抽象的指令集進行操做,而且指令選擇部分發生在代碼生成以後。彙編程序以半抽象形式工做,所以當您看到相似MOV 工具鏈實際上爲該操做生成的內容可能根本不是移動指令,多是清除指令或加載指令。或者它可能與該名稱的機器指令徹底對應。一般,特定於機器的操做傾向於本身出現,而更通用的概念(如內存移動和子例程調用與返回)則更爲抽象。具體細節因架構而異,咱們對此不嚴謹深表歉意。狀況尚不明確。golang

彙編程序是解析該半抽象指令集的描述並將其轉換爲要輸入到連接器的指令的一種方式。若是要查看給定體系結構(例如amd64)的彙編指令的外觀,則標準庫的源代碼中有許多示例,例如 runtimemath/big。您還能夠檢查編譯器做爲彙編代碼發出的內容(實際輸出可能與您在此處看到的有所不一樣):編程

$ cat x.go
package main

func main() {
    println(3)
}
$ GOOS=linux GOARCH=amd64 go tool compile -S x.go        
# or: go build -gcflags -S x.go
"".main STEXT size=74 args=0x0 locals=0x10
    0x0000 00000 (x.go:3)    TEXT    "".main(SB), $16-0
    0x0000 00000 (x.go:3)    MOVQ    (TLS), CX
    0x0009 00009 (x.go:3)    CMPQ    SP, 16(CX)
    0x000d 00013 (x.go:3)    JLS    67
    0x000f 00015 (x.go:3)    SUBQ    $16, SP
    0x0013 00019 (x.go:3)    MOVQ    BP, 8(SP)
    0x0018 00024 (x.go:3)    LEAQ    8(SP), BP
    0x001d 00029 (x.go:3)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (x.go:3)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (x.go:3)    FUNCDATA    $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (x.go:4)    PCDATA    $0, $0
    0x001d 00029 (x.go:4)    PCDATA    $1, $0
    0x001d 00029 (x.go:4)    CALL    runtime.printlock(SB)
    0x0022 00034 (x.go:4)    MOVQ    $3, (SP)
    0x002a 00042 (x.go:4)    CALL    runtime.printint(SB)
    0x002f 00047 (x.go:4)    CALL    runtime.printnl(SB)
    0x0034 00052 (x.go:4)    CALL    runtime.printunlock(SB)
    0x0039 00057 (x.go:5)    MOVQ    8(SP), BP
    0x003e 00062 (x.go:5)    ADDQ    $16, SP
    0x0042 00066 (x.go:5)    RET
    0x0043 00067 (x.go:5)    NOP
    0x0043 00067 (x.go:3)    PCDATA    $1, $-1
    0x0043 00067 (x.go:3)    PCDATA    $0, $-1
    0x0043 00067 (x.go:3)    CALL    runtime.morestack_noctxt(SB)
    0x0048 00072 (x.go:3)    JMP    0
...

FUNCDATAPCDATA指令包含用於經過垃圾收集器的使用的信息; 它們由編譯器引入。
要查看連接後放入二進制文件的內容,請使用go tool objdump閉包

$ go build -o x.exe x.go
$ go tool objdump -s main.main x.exe
TEXT main.main(SB) /tmp/x.go
  x.go:3        0x10501c0        65488b0c2530000000    MOVQ GS:0x30, CX
  x.go:3        0x10501c9        483b6110        CMPQ 0x10(CX), SP
  x.go:3        0x10501cd        7634            JBE 0x1050203
  x.go:3        0x10501cf        4883ec10        SUBQ $0x10, SP
  x.go:3        0x10501d3        48896c2408        MOVQ BP, 0x8(SP)
  x.go:3        0x10501d8        488d6c2408        LEAQ 0x8(SP), BP
  x.go:4        0x10501dd        e86e45fdff        CALL runtime.printlock(SB)
  x.go:4        0x10501e2        48c7042403000000    MOVQ $0x3, 0(SP)
  x.go:4        0x10501ea        e8e14cfdff        CALL runtime.printint(SB)
  x.go:4        0x10501ef        e8ec47fdff        CALL runtime.printnl(SB)
  x.go:4        0x10501f4        e8d745fdff        CALL runtime.printunlock(SB)
  x.go:5        0x10501f9        488b6c2408        MOVQ 0x8(SP), BP
  x.go:5        0x10501fe        4883c410        ADDQ $0x10, SP
  x.go:5        0x1050202        c3            RET
  x.go:3        0x1050203        e83882ffff        CALL runtime.morestack_noctxt(SB)
  x.go:3        0x1050208        ebb6            JMP main.main(SB)

常數

儘管彙編程序從Plan 9彙編程序得到指導,但這是一個獨立的程序,所以存在一些差別。一種是不斷評估。彙編器中的常量表達式是使用Go的運算符優先級解析的,而不是原始的相似於C的優先級。所以3&1<<2爲4,而不是0-解析爲(3&1)<<2 not 3&(1<<2)。一樣,常量始終被評估爲64位無符號整數。所以-2,不是整數值減去2,而是具備相同位模式的無符號64位整數。區分不多有關係,但要避免在設置右操做數的高位時避免模棱兩可,除法或右移。架構

符號

一些符號(例如R1LR)是預約義的,並引用寄存器。確切的設置取決於體系結構。
有四個預先聲明的符號引用僞寄存器。這些不是真正的寄存器,而是由工具鏈維護的虛擬寄存器,例如幀指針。僞寄存器的集合對於全部體系結構都是相同的:框架

  • FP:框架指針:參數和局部變量。
  • PC:程序計數器:跳轉和分支。
  • SB:靜態基本指針:全局符號。
  • SP:堆棧指針:堆棧頂部。
    全部用戶定義的符號均做爲僞寄存器FP(參數和局部變量)和SB(全局變量)的偏移量寫入 。

SB僞寄存器能夠被認爲是記憶的原點上,因此符號foo(SB) 的名稱是foo在內存中的地址。該表格用於命名全局功能和數據。將<>名稱添加到中(如中)foo<>(SB),使名稱僅在當前源文件中可見,例如staticC文件中的頂級聲明。在名稱中添加偏移量是指距符號地址的偏移量,所以 foo+4(SB)距的開頭四個字節。函數

FP僞寄存器是用來指函數參數的虛擬幀指針。編譯器維護一個虛擬幀指針,並將堆棧上的參數引用爲該僞寄存器的偏移量。所以,0(FP)是該函數的第一個參數, 8(FP)是第二個參數(在64位計算機上),依此類推。可是,以這種方式引用函數自變量時,必須在first_arg+0(FP)和開頭放置一個名稱second_arg+8(FP)。(偏移量的含義(與幀指針的偏移量不一樣)與它與with的用法不一樣SB,此處偏移量是符號的偏移量。)彙編程序強制執行此約定,拒絕plain0(FP)8(FP)。實際名稱在語義上可有可無,但應用於記錄自變量名稱。值得強調的是FP 即便在具備硬件幀指針的體系結構上,始終是僞寄存器,而不是硬件寄存器。工具

對於帶有Go原型的彙編函數,go vet將檢查參數名稱和偏移量是否匹配。在32位系統上,經過在名稱中添加a_lo_hi後綴來區分64位值的低32位和高32位,如arg_lo+0(FP)或中所示arg_hi+4(FP)。若是Go原型未命名其結果,則預期的程序集名稱爲retoop

SP僞寄存器是用來指幀局部變量的虛擬堆棧指針和函數調用正在編寫的參數。它指向本地堆棧幀的頂,因此引用應在範圍[-framesize,0)使用負偏移: x-8(SP)y-4(SP),等。

在具備名爲的硬件寄存器的體系結構上SP,名稱前綴將對虛擬堆棧指針的引用與對體系結構SP寄存器的引用區分開 。也就是說,x-8(SP)而且-8(SP) 是不一樣的內存位置:第一個引用虛擬堆棧指針僞寄存器,而第二個引用硬件的SP寄存器。

在一些機器上SP,並PC在傳統的物理,地址寄存器中的別名,在圍棋彙編的名稱SPPC 仍然特殊處理; 例如,引用SP須要一個符號,就像FP。要訪問實際的硬件寄存器,請使用真實R名稱。例如,ARM架構的硬件 SPPC是可訪問 R13R15

分支和直接跳轉老是寫爲PC的偏移量或標籤的跳轉:

label:
    MOVW $0, R1
    JMP label

每一個標籤僅在定義它的函數中可見。所以,容許文件中的多個功能定義和使用相同的標籤名稱。直接跳轉和調用指令能夠定位文本符號,例如name(SB),但不能定位符號的偏移量,例如name+4(SB)

指令,寄存器和彙編器指令始終位於大寫字母中,以提醒您彙編編程是一項艱鉅的工做。(例外:gARM上的寄存器重命名。)

在Go目標文件和二進制文件中,符號的全名是程序包路徑,後跟一個句點和符號名稱: fmt.Printfmath/rand.Int。因爲彙編程序的解析器將句點和斜槓視爲標點符號,所以這些字符串不能直接用做標識符名稱。相反,彙編器容許在標識符中使用中間點字符U + 00B7和分隔斜槓U + 2215,並將它們重寫爲純句點和斜槓。在彙編程序源文件中,以上符號表示爲 fmt·Printfmath∕rand·Int。編譯器在使用-S標誌時生成的彙編清單直接顯示了句點和斜槓,而不是彙編程序要求的Unicode替換。

大多數手寫的彙編文件都沒有在符號名稱中包含完整的程序包路徑,由於連接器會在句點開始的任何源名稱的開頭插入當前對象文件的程序包路徑:在math / rand中的彙編源文件中包實現中,包的Int函數能夠稱爲·Int。這種約定避免了在本身的源代碼中對包的導入路徑進行硬編碼的須要,從而使將代碼從一個位置移動到另外一個位置變得更加容易。

指令

彙編器使用各類指令將文本和數據綁定到符號名稱。例如,這是一個簡單的完整函數定義。該TEXT 僞指令聲明符號runtime·profileloop和後面的指令構成函數的主體。TEXT塊中的最後一條指令必須是某種形式的跳轉,一般是RET(僞)指令。(若是不是,則連接器將追加一個跳轉至自身的指令;中不存在任何穿透TEXTs。)在符號以後,參數是標誌(請參見下文)和幀大小,常數(但請參見下文):

文本運行時·profileloop(SB),NOSPLIT,$ 8 MOVQ $ runtime·profileloop1(SB),CX MOVQ CX,0(SP) CALL運行時·外部線程處理程序(SB) RET

在通常狀況下,幀大小後跟參數大小,並用減號分隔。(這不是減法,只是特有的語法。)框架大小$24-8指出該函數具備24字節的框架,並使用8個字節的參數進行調用,該參數位於調用方的框架上。若是NOSPLIT未爲指定TEXT,則必須提供參數大小。對於帶有Go原型的彙編函數,go vet將檢查參數大小是否正確。

請注意,符號名稱使用中間的點分隔組件,並被指定爲與靜態基本僞寄存器的偏移量SB。該函數將runtime使用簡單名稱從Go源代碼中調用以進行打包profileloop

全局數據符號由一系列初始化DATA指令及其 後的GLOBL指令定義。每一個DATA指令都會初始化相應內存的一部分。未顯式初始化的內存將清零。DATA指令的通常形式是

數據符號+偏移量(SB)/寬度,值

它以給定的偏移量和寬度使用給定的值初始化符號存儲。DATA給定符號的指令必須以增長的偏移量編寫。

GLOBL指令將符號聲明爲全局符號。參數是可選標誌,數據的大小聲明爲全局,除非DATA指令已將其初始化,不然其初始值爲全零。該GLOBL指令必須遵循任何相應的DATA指令。

例如,

數據divtab <> + 0x00(SB)/ 4,$ 0xf4f8fcff 數據divtab <> + 0x04(SB)/ 4,$ 0xe6eaedf0 ... 數據divtab <> + 0x3c(SB)/ 4,$ 0x81828384 GLOBL divtab <>(SB),RODATA,64美圓
GLOBL運行時·tlsoffset(SB),NOPTR,$ 4

聲明並初始化divtab<>一個4字節整數值的只讀64字節表,並聲明runtime·tlsoffset一個不包含指針的4字節隱式清零變量。

指令可能有一個或兩個參數。若是有兩個,則第一個是標誌的位掩碼,能夠將這些標誌寫爲數字表達式,或者加在一塊兒或累加起來,或者能夠進行符號設置以方便人類吸取。在標準#include 文件中定義的它們的值textflag.h是:

  • NOPROF= 1
    (對於TEXT項目。)不要分析標記的功能。不推薦使用此標誌。
  • DUPOK= 2
    在單個二進制文件中具備此符號的多個實例是合法的。連接器將選擇要使用的重複項之一。
  • NOSPLIT= 4
    (對於TEXT項。)請勿插入序言以檢查是否必須拆分堆棧。例程的框架及其所調用的全部內容必須適合堆棧段頂部的備用空間。用於保護例程,例如堆棧拆分代碼自己。
  • RODATA= 8
    (用於DATAGLOBL。)將此數據放在只讀部分中。
  • NOPTR= 16
    (用於DATAGLOBL項目。)此數據不包含指針,所以不須要由垃圾收集器進行掃描。
  • WRAPPER= 32
    (對於TEXT項。)這是包裝函數,不該算做禁用recover
  • NEEDCTXT= 64
    (對於TEXT項。)此函數是一個閉包,所以它將使用其傳入的上下文寄存器。
  • LOCAL= 128
    此符號位於動態共享庫的本地。
  • TLSBSS= 256
    (用於DATAGLOBL項目。)將此數據放入線程本地存儲中。
  • NOFRAME= 512
    (對於TEXT項。)即便這不是葉函數,也不要插入指令來分配堆棧幀並保存/恢復返回地址。僅在聲明幀大小爲0的函數上有效。
  • TOPFRAME= 2048
    (對於TEXT項。)函數是調用堆棧的頂部。回溯應在此功能處中止。
相關文章
相關標籤/搜索