這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
源自 Plan9 彙編實現。 儲存在 .s 檔案中,編譯器自動編譯、連結。
本文內容基於 amd64 架構。
指令
指令參數長度。
MOVB: 1-byteMOVW: 2 MOVL: 4 MOVQ: 8
資料移動方向:從左往右。
ADD R1, R2 // R2 += R1 SUB R3, R4 // R4 -= R3 SUB R3, R4, R5 // R5 = R4 - R3 MUL $7, R6 // R6 *= 7
記憶體訪問。
MOV (R1), R2 // R2 = *R1 MOV 8(R1), R2 // R2 = *(8 + R2) MOV 16(R1)(R2*2), R3 // R3 = *(16 + R1 + R2*2) MOV runtime·x(SB), R2 // R2 = *runtime·x
跳轉指令。
JMP label // 跳轉到標籤。 JMP 2(PC) // 跳轉到 PC + n 行。 JMP -2(PC)
數字常量以 $ 開頭,十進位($10)和 十六進位($0x10)。 標籤僅在函數內有效。
偽寄存器
偽寄存器(pseudo-register)由語言定義並使用,最終會被編譯為硬體寄存器引用。
考慮到平台差異,編譯後的機器代碼,可能須儲存 PC、BP、SP 等物理寄存器值。 在編寫彙編代碼時,很難事先計算好實際所需位移量。為此,組合語言用偽寄存器表示某個相對位置就很有必要。
- SB: Static Base Pointer(全域符號)
表示一個全域符號地址,通常應用於全域函數或資料。
例如
CALL add(SB)
表示對應符號名字為 add 的記憶體位址。
在名字後添加角括弧(add<>(SB)
),表示該符號名僅在當前檔案內可見。
還可用位移量表示基於某個符號名字的地址,例如 add+8(SB)
。
- FP: Frame Pointer(參數地址)
指向由調用方提供的參數列表起始地址,通過位移量指向不同參數或傳回值。
通常在位移量前包含參數名。例如
MOVQ size+16(FP), AX
- SP: Stack Pointer (棧局部變數記憶體位址)
偽 SP 寄存器表示棧幀內,用於本地局部變數操作的起始地址。
鑒於棧從底開始的操作方式,SP 實際是棧底位置(等同調整後的 BP 地址)。
使用該方式訪問局部變數,須添加變數名,如
x-8(SP)
。如果省略變數名,則表示硬體寄存器。
- PC: Program Counter(指令地址)
可用來按指令行數條轉。
比如
JMP 2(PC)
表示以當前位置為 0 基準,往下跳到第 2 行。
考慮到棧幀記憶體實際上分成局部變數(底)和調用參數(頂)兩部分使用,所以用偽 SP 寄存器負值便宜訪問局部變數是很自然的做法。 如此,物理寄存器 SP 用來操作調用參數入棧;而偽寄存器 SP 用來訪問局部變數。 畢竟 BP 寄存器是可選的。
注意 x+0(FP)
和 gobuf_pc(AX)
宏函數的區別。
CALLEE lo SP +-----------+ .......................... | | . +-----------+ . | | frame size(包括 caller BP) BP (pseudo SP) +-----------+ . | caller BP | . +-----------+ .......................... | caller PC | FP +-----------+------------+ SP .......... | arg0 | call arg0 | . +-----------+------------+ . | argn | call argn | argument size +-----------+------------+ . | return | call ret | . hi +-----------+------------+ ............. | local var0 | +------------+ | local varn | +------------+ BP (pseudo SP) CALLER
函數
函數定義。
參數及傳回值大小 | TEXT runtime·cgocallback(SB),NOSPLIT,$32-32 | | | 包名 函數名 棧幀大小(不包括參數及傳回值)
當前包,可省略包名,直接以中心點開始。
由調用者(caller)負責分配目標函數(callee)參數和傳回值記憶體。 調用者須自行儲存相關寄存器狀態。
樣本
使用彙編代碼編寫一個簡單的加法。
add.s
#include "textflag.h" // add(x, y int) int TEXT ·add(SB), NOSPLIT, $8-24 MOVQ $0, z-0x8(SP) MOVQ x+0x0(FP), AX MOVQ y+0x8(FP), BX ADDQ AX, BX MOVQ BX, z-0x8(SP) MOVQ BX, ret+0x10(FP) RET
main.go
package main func add(x, y int) (z int) // 聲明彙編函數原型 func main() { z := add(0x100, 0x200) println(z) }
可以看到編譯器插入棧幀調整,環境儲存等指令。
$ go build -gcflags "-l" $ go tool objdump -s "main\.add" test TEXT main.add(SB) add.s add.s:5 0x104bfe0 SUBQ $0x10, SP // 因為要儲存 BP,所以棧幀大小調整到 0x10。 add.s:5 0x104bfe4 MOVQ BP, 0x8(SP) add.s:5 0x104bfe9 LEAQ 0x8(SP), BP add.s:6 0x104bfee MOVQ $0x0, 0(SP) add.s:7 0x104bff6 MOVQ 0x18(SP), AX add.s:8 0x104bffb MOVQ 0x20(SP), BX add.s:9 0x104c000 ADDQ AX, BX add.s:10 0x104c003 MOVQ BX, 0(SP) add.s:11 0x104c007 MOVQ BX, 0x28(SP) add.s:12 0x104c00c MOVQ 0x8(SP), BP add.s:12 0x104c011 ADDQ $0x10, SP // 清除棧幀。 add.s:12 0x104c015 RET
使用 -gcflags -S
輸出反組譯碼時,會有 FUNCDATA 和 PCDATA 資訊。 它們是編譯器引入,包含記憶體回收行程要使用的資訊。
323 次點擊