最近升級 go1.9,發現一個獲取 goroutine id 的依賴沒有支持1.9,因而手動寫了一個,順便學習一下 go assembly。但願你看完這篇文章後,對go彙編有必定的瞭解。git
首先安利一個獲取當前goroutine id 的library, gid,支持 go1.7 - go1.9, 多是目前最小的庫了,使用也很簡單: id := gid.Get()
。github
Go彙編語法相似 Plan 9,它不是對機器語言的直接表達,擁有半抽象的指令集。整體來講, machine-specific 操做通常就是它們的本意,其餘概念例如 memory move, subroutine call, return 是抽象的表達。架構
evaluation 優先級和 C 不一樣,例如 3&1<<2 == 4, 解釋爲 (3&1) << 2。
常量被認爲是 unsigned 64-bit int, 所以 -2 不是負數,而是被做爲 uint64 解讀。app
4個預約義的符號,表示 pseudo-registers, 僞寄存器(虛擬寄存器?)。函數
用戶定義的符號都是經過偏移(offset)來表示的。oop
SB寄存器表示全局內存起點,foo(SB) 表示 符號foo做爲內存地址使用。這種形式用於命名 全局函數,數據。<>
限制符號只能在當前源文件使用,相似 C 中的 static。foo+4(SB)
表示foo 日後 4字節的地址。學習
FP寄存器指向函數參數。0(FP)是第一個參數,8(FP)是第二個參數(64-bit machine). first_arg+0(FP)
表示把第一個參數地址綁定到符號 first_arg, 這個與SB的含義不一樣。ui
SP寄存器表示棧指針,指向 top of local stack frame, 因此 offset 都是負數,範圍在 [ -framesize, 0 ), 例如 x-8(SP). 對於硬件寄存器名稱爲SP的架構,x-8(SP)
表示虛擬棧指針寄存器, -8(SP)
表示硬件 SP 寄存器.this
跳轉和分支是針對PC的offset,或者 label, 例如:atom
label: MOVW $0, R1 JMP label
label 範圍是函數級別的,不一樣函數能夠定義相同名稱的label。
例如:
TEXT runtime·profileloop(SB),NOSPLIT,$8 MOVQ $runtime·profileloop1(SB), CX MOVQ CX, 0(SP) CALL runtime·externalthreadhandler(SB) RET
TEXT 指令定義符號 runtime·profileloop
, RET 表示結尾,若是沒聲明,linker會添加 jump-to-self 指令。
$8 表示 frame size,通常後面須要加上參數大小。這裏由於有 NOSPLIT,能夠不加。
全局數據符號用 DATA 聲明,方式爲 DATA symbol+offset(SB)/width, value
GLOBL 定義數據爲全局。例如:
DATA divtab<>+0x00(SB)/4, $0xf4f8fcff DATA divtab<>+0x04(SB)/4, $0xe6eaedf0 ... DATA divtab<>+0x3c(SB)/4, $0x81828384 GLOBL divtab<>(SB), RODATA, $64 GLOBL runtime·tlsoffset(SB), NOPTR, $4
定義並初始化了 divtab<>, 一個 只讀的 64字節 表,每一項4字節。定義了 runtime·tlsoffset, 4字節空值,非指針。
指令有一個或兩個參數。若是有兩個,第一個是 bit mask, 能夠爲數字表達式。值的定義以下:
//main.go package main import "fmt" func add(x, y int64) int64 func main() { fmt.Println(add(2, 3)) }
// add.s TEXT ·add(SB),$0-24 MOVQ x+0(FP), BX MOVQ y+8(FP), BP ADDQ BP, BX MOVQ BX, ret+16(FP) RET
定義一個函數的方式爲: TEXT package_name·function_name(SB),$frame_size-arguments_size
例子中 package_name 是空,表示當前package。 以後是一個 middle point(U+00B7) 和 函數名稱。
frame_size 是 $0, 表示了須要 stack 的空間大小,這裏是0, 表示不須要stack,只使用 寄存器。函數的參數和返回值的大小爲 3 * 8 = 24
bytes。
MOVQ
表示移動一個 64bit 的值(Q 表明 quadword)。這裏是從 FP(frame pointer, 指向 函數參數的起始位置) 移動到 BX
和 BP
. 語法 symbol+offset(register)
中的 offset, 表明了從 register 爲起點,移動 offset後的地址。這裏的 x, y 是在函數定義中的參數符號。
ADDQ
那一行指令 表示把兩個 64bit register的值相加,存到 BX。
最後的 MOVQ
把 BX 中的值,移動到 FP+16的位置, 這裏的 ret
符號是編譯器默認的返回值符號。
package main import _ "fmt" func hello() func main(){ hello() }
#include "textflag.h" DATA world<>+0(SB)/8, $"hello wo" DATA world<>+8(SB)/4, $"rld " GLOBL world<>+0(SB), RODATA, $12 // 須要 stack空間 88字節,沒有參數和返回值 TEXT ·hello(SB),$88-0 SUBQ $88, SP MOVQ BP, 80(SP) LEAQ 80(SP), BP // 建立字符,存在 my_string LEAQ world<>+0(SB), AX MOVQ AX, my_string+48(SP) MOVQ $11, my_string+56(SP) MOVQ $0, autotmp_0+64(SP) MOVQ $0, autotmp_0+72(SP) LEAQ type·string(SB), AX MOVQ AX, (SP) LEAQ my_string+48(SP), AX MOVQ AX, 8(SP) // 建立一個 interface CALL runtime·convT2E(SB) MOVQ 24(SP), AX MOVQ 16(SP), CX MOVQ CX, autotmp_0+64(SP) MOVQ AX, autotmp_0+72(SP) LEAQ autotmp_0+64(SP), AX MOVQ AX, (SP) MOVQ $1, 8(SP) MOVQ $1, 16(SP) // 調用 fmt.Println CALL fmt·Println(SB) MOVQ 80(SP), BP ADDQ $88, SP RET
第一行的 #include
加載一些常量,這裏咱們將用到 RODATA
.
DATA
用於在內存中存儲字符串,一次能夠存儲 1,2,4或8 字節。在符號後的<>
做用是限制數據在當前文件使用。
GLOBL
將數據設爲全局,只讀,相對位置12.
gid 庫中用到的函數
#include "go_asm.h" #include "go_tls.h" #include "textflag.h" // 返回值 8 bytes, 符號爲 getg TEXT ·getg(SB), NOSPLIT, $0-8 // get_tls 的宏爲: #define get_tls(r) MOVQ TLS, r // 等價於 MOVQ TLS, CX // 從 TLS(Thread Local Storage) 起始移動 8 byte 值 到 CX 寄存器 get_tls(CX) // g的宏爲: g(r) 0(r)(TLS*1) // 等價於 0(CX)(TLS*1), AX // 查到意義爲 indexed with offset, 這裏 offset=0, 索引是什麼意思不清楚 MOVQ g(CX), AX // 從AX起始移動 8 byte 值,到ret符號的位置 MOVQ AX, ret+0(FP) RET
一個原子交換 int32 的函數
package atomic import ( "unsafe" ) func SwapInt32(addr *int32, new int32) (old int32)
#include "textflag.h" // 參數大小 = 8 + 4 + 4 , + 4 (默認的 ret符號?) TEXT ·SwapInt32(SB),NOSPLIT,$0-20 JMP ·SwapUint32(SB) TEXT ·SwapUint32(SB),NOSPLIT,$0-20 // 第一個參數 移動 8 byte 到 BP MOVQ addr+0(FP), BP // 第二個參數 移動 4 byte 到 AX MOVL new+8(FP), AX // 原子操做, write-after-read, 把 (AX, offset=0) 與 (BP, offset=0) 交換 4 byte 數據 XCHGL AX, 0(BP) // 移動 AX 到 old 符號 MOVL AX, old+16(FP) RET