Go Assembly 學習筆記

最近升級 go1.9,發現一個獲取 goroutine id 的依賴沒有支持1.9,因而手動寫了一個,順便學習一下 go assembly。但願你看完這篇文章後,對go彙編有必定的瞭解。git

Go Assembly

首先安利一個獲取當前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, 僞寄存器(虛擬寄存器?)。函數

  • FP: frame pointer, 參數和本地變量
  • PC: program counter: 跳轉,分支
  • SB: static base pointer: 全局符號
  • SP: stack pointer: 棧頂

用戶定義的符號都是經過偏移(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, 能夠爲數字表達式。值的定義以下:

  • NOPROF = 1 ; (For TEXT items.) Don't profile the marked function. This flag is deprecated. 廢棄
  • DUPOK = 2 ; It is legal to have multiple instances of this symbol in a single binary. The linker will choose one of the duplicates to use. 此符號容許存在多個,連接器選擇其一使用。
  • NOSPLIT = 4 ; (For TEXT items.) Don't insert the preamble to check if the stack must be split. The frame for the routine, plus anything it calls, must fit in the spare space at the top of the stack segment. Used to protect routines such as the stack splitting code itself. 不插入代碼,不檢查是否須要 stack split. (疑問,高版本go使用連續棧,這個指令還有做用嗎?)
  • RODATA = 8 ; (For DATA and GLOBL items.) Put this data in a read-only section. 數據存入只讀區
  • NOPTR = 16 ; (For DATA and GLOBL items.) This data contains no pointers and therefore does not need to be scanned by the garbage collector. 表示非指針,不須要 GC。
  • WRAPPER = 32 ; (For TEXT items.) This is a wrapper function and should not count as disabling recover.
  • NEEDCTXT = 64 ; (For TEXT items.) This function is a closure so it uses its incoming context register.

Example: Add

//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, 指向 函數參數的起始位置) 移動到 BXBP. 語法 symbol+offset(register) 中的 offset, 表明了從 register 爲起點,移動 offset後的地址。這裏的 x, y 是在函數定義中的參數符號。

ADDQ 那一行指令 表示把兩個 64bit register的值相加,存到 BX。

最後的 MOVQ 把 BX 中的值,移動到 FP+16的位置, 這裏的 ret 符號是編譯器默認的返回值符號。

Example: Hello

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.

Example: gid

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

Example: SwapInt32

一個原子交換 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
相關文章
相關標籤/搜索