go plan9彙編入門
原文地址: https://yuchanns.org/posts/2020/01/31/golang-assembly/
歡迎訪問我的博客 yuchanns'Atelier
有時候我們想要知道寫出來的代碼是怎麼編譯執行的,這時候 go tool compile
就是一個很好用的工具。
本文相關代碼 yuchanns/gobyexample
如何輸出彙編代碼
有三種方法可以輸出go代碼的彙編代碼:
- go tool compile 生成obj文件
- go build -gcflags 生成最終二進制文件
-
先go build然後在go tool objdump 對二進制文件進行反彙編
當然,具體行爲還需要在這些命令後面加上具體的flag
。flag的內容可以通過查閱 官方文檔 獲得。
本文涉及Flags說明
-N 禁止優化
-S 輸出彙編代碼
-l 禁止內聯
什麼是內聯
如果學過 c/c++
就知道,通過 inline
關鍵字修飾的函數叫做內聯函數。內聯函數的優勢是在編譯過程中直接展開函數中的代碼,將其替換到源碼的函數調用位置,這樣可以節省函數調用的消耗,提高運行速度。適用於函數體短小且頻繁調用的函數,如果函數體太大了,會增大目標代碼。是一種空間換時間的做法。
go編譯器會智能判斷對代碼進行優化和使用匯編。而我們在分析學習代碼調用情況的時候需要禁止掉這些優化,避免混淆理解。
以下我們使用 go build -gcflags="-N -l -S" file
來獲得彙編代碼。
獲取一份簡單的彙編代碼
現在手上有一份關於range的代碼,但是我們運行之後出現了一些問題 [1] [2] :
package assembly import "fmt" func RangeClause() { arr := []int{1, 2, 3} var newArr []*int for _, v := range arr { newArr = append(newArr, &v) } for _, v := range newArr { fmt.Println(*v) } }
結果輸出了三個3。
也許我們在學習過程中見過類似的錯誤,然後設法(或者別人告訴我們怎麼)避免錯誤,但是仍然百思不得其解,知其然不知其所以然。
這時候獲取彙編代碼就可以排上用場了。
執行 go build -gcflags="-N -l -S" range_clause.go
,得到下面這份輸出結果:
"".RangeClause STEXT size=842 args=0x0 locals=0x158 0x0000 00000 (range_clause.go:5) TEXT "".RangeClause(SB), ABIInternal, $344-0 0x0000 00000 (range_clause.go:5) MOVQ (TLS), CX 0x0009 00009 (range_clause.go:5) LEAQ -216(SP), AX 0x0011 00017 (range_clause.go:5) CMPQ AX, 16(CX) 0x0015 00021 (range_clause.go:5) JLS 832 0x001b 00027 (range_clause.go:5) SUBQ $344, SP 0x0022 00034 (range_clause.go:5) MOVQ BP, 336(SP) 0x002a 00042 (range_clause.go:5) LEAQ 336(SP), BP 0x0032 00050 (range_clause.go:5) FUNCDATA $0, gclocals·f0a67958015464e4cc8847ce0df60843(SB) 0x0032 00050 (range_clause.go:5) FUNCDATA $1, gclocals·1be50b3ff1c6bce621b19ced5cafc212(SB) 0x0032 00050 (range_clause.go:5) FUNCDATA $2, gclocals·160a1dd0c9595e8bcf8efc4c6b948d91(SB) 0x0032 00050 (range_clause.go:5) FUNCDATA $3, "".RangeClause.stkobj(SB) 0x0032 00050 (range_clause.go:6) PCDATA $0, $1 0x0032 00050 (range_clause.go:6) PCDATA $1, $0 0x0032 00050 (range_clause.go:6) LEAQ ""..autotmp_9+120(SP), AX 0x0037 00055 (range_clause.go:6) PCDATA $1, $1 0x0037 00055 (range_clause.go:6) MOVQ AX, ""..autotmp_8+152(SP) 0x003f 00063 (range_clause.go:6) PCDATA $0, $0 0x003f 00063 (range_clause.go:6) TESTB AL, (AX) 0x0041 00065 (range_clause.go:6) MOVQ ""..stmp_0(SB), AX 0x0048 00072 (range_clause.go:6) MOVQ AX, ""..autotmp_9+120(SP) 0x004d 00077 (range_clause.go:6) MOVUPS ""..stmp_0+8(SB), X0 0x0054 00084 (range_clause.go:6) MOVUPS X0, ""..autotmp_9+128(SP) 0x005c 00092 (range_clause.go:6) PCDATA $0, $1 0x005c 00092 (range_clause.go:6) PCDATA $1, $0 0x005c 00092 (range_clause.go:6) MOVQ ""..autotmp_8+152(SP), AX 0x0064 00100 (range_clause.go:6) TESTB AL, (AX) 0x0066 00102 (range_clause.go:6) JMP 104 0x0068 00104 (range_clause.go:6) PCDATA $0, $0 0x0068 00104 (range_clause.go:6) PCDATA $1, $2 0x0068 00104 (range_clause.go:6) MOVQ AX, "".arr+240(SP) 0x0070 00112 (range_clause.go:6) MOVQ $3, "".arr+248(SP) 0x007c 00124 (range_clause.go:6) MOVQ $3, "".arr+256(SP) 0x0088 00136 (range_clause.go:7) PCDATA $1, $3 0x0088 00136 (range_clause.go:7) MOVQ $0, "".newArr+216(SP) 0x0094 00148 (range_clause.go:7) XORPS X0, X0 0x0097 00151 (range_clause.go:7) MOVUPS X0, "".newArr+224(SP) 0x009f 00159 (range_clause.go:8) PCDATA $0, $1 0x009f 00159 (range_clause.go:8) LEAQ type.int(SB), AX 0x00a6 00166 (range_clause.go:8) PCDATA $0, $0 0x00a6 00166 (range_clause.go:8) MOVQ AX, (SP) 0x00aa 00170 (range_clause.go:8) CALL runtime.newobject(SB) 0x00af 00175 (range_clause.go:8) PCDATA $0, $1 0x00af 00175 (range_clause.go:8) MOVQ 8(SP), AX 0x00b4 00180 (range_clause.go:8) PCDATA $0, $0 0x00b4 00180 (range_clause.go:8) PCDATA $1, $4 0x00b4 00180 (range_clause.go:8) MOVQ AX, "".&v+192(SP) 0x00bc 00188 (range_clause.go:8) MOVQ "".arr+256(SP), AX 0x00c4 00196 (range_clause.go:8) MOVQ "".arr+248(SP), CX 0x00cc 00204 (range_clause.go:8) PCDATA $0, $2 0x00cc 00204 (range_clause.go:8) PCDATA $1, $5 0x00cc 00204 (range_clause.go:8) MOVQ "".arr+240(SP), DX 0x00d4 00212 (range_clause.go:8) PCDATA $0, $0 0x00d4 00212 (range_clause.go:8) PCDATA $1, $6 0x00d4 00212 (range_clause.go:8) MOVQ DX, ""..autotmp_5+288(SP) 0x00dc 00220 (range_clause.go:8) MOVQ CX, ""..autotmp_5+296(SP) 0x00e4 00228 (range_clause.go:8) MOVQ AX, ""..autotmp_5+304(SP) 0x00ec 00236 (range_clause.go:8) MOVQ $0, ""..autotmp_10+112(SP) 0x00f5 00245 (range_clause.go:8) MOVQ ""..autotmp_5+296(SP), AX 0x00fd 00253 (range_clause.go:8) MOVQ AX, ""..autotmp_11+104(SP) 0x0102 00258 (range_clause.go:8) JMP 260 0x0104 00260 (range_clause.go:8) MOVQ ""..autotmp_11+104(SP), CX 0x0109 00265 (range_clause.go:8) CMPQ ""..autotmp_10+112(SP), CX 0x010e 00270 (range_clause.go:8) JLT 277 0x0110 00272 (range_clause.go:8) JMP 516 0x0115 00277 (range_clause.go:8) MOVQ ""..autotmp_10+112(SP), CX 0x011a 00282 (range_clause.go:8) SHLQ $3, CX 0x011e 00286 (range_clause.go:8) PCDATA $0, $3 0x011e 00286 (range_clause.go:8) ADDQ ""..autotmp_5+288(SP), CX 0x0126 00294 (range_clause.go:8) PCDATA $0, $0 0x0126 00294 (range_clause.go:8) MOVQ (CX), CX 0x0129 00297 (range_clause.go:8) MOVQ CX, ""..autotmp_12+96(SP) 0x012e 00302 (range_clause.go:8) PCDATA $0, $2 0x012e 00302 (range_clause.go:8) MOVQ "".&v+192(SP), DX 0x0136 00310 (range_clause.go:8) PCDATA $0, $0 0x0136 00310 (range_clause.go:8) MOVQ CX, (DX) 0x0139 00313 (range_clause.go:9) PCDATA $0, $3 0x0139 00313 (range_clause.go:9) MOVQ "".&v+192(SP), CX 0x0141 00321 (range_clause.go:9) PCDATA $0, $0 0x0141 00321 (range_clause.go:9) PCDATA $1, $7 0x0141 00321 (range_clause.go:9) MOVQ CX, ""..autotmp_13+184(SP) 0x0149 00329 (range_clause.go:9) MOVQ "".newArr+232(SP), CX 0x0151 00337 (range_clause.go:9) MOVQ "".newArr+224(SP), DX 0x0159 00345 (range_clause.go:9) PCDATA $0, $4 0x0159 00345 (range_clause.go:9) PCDATA $1, $8 0x0159 00345 (range_clause.go:9) MOVQ "".newArr+216(SP), BX 0x0161 00353 (range_clause.go:9) LEAQ 1(DX), SI 0x0165 00357 (range_clause.go:9) CMPQ SI, CX 0x0168 00360 (range_clause.go:9) JLS 364 0x016a 00362 (range_clause.go:9) JMP 446 0x016c 00364 (range_clause.go:9) PCDATA $0, $-2 0x016c 00364 (range_clause.go:9) PCDATA $1, $-2 0x016c 00364 (range_clause.go:9) JMP 366 0x016e 00366 (range_clause.go:9) PCDATA $0, $5 0x016e 00366 (range_clause.go:9) PCDATA $1, $9 0x016e 00366 (range_clause.go:9) MOVQ ""..autotmp_13+184(SP), AX 0x0176 00374 (range_clause.go:9) PCDATA $0, $6 0x0176 00374 (range_clause.go:9) LEAQ (BX)(DX*8), DI 0x017a 00378 (range_clause.go:9) PCDATA $0, $-2 0x017a 00378 (range_clause.go:9) PCDATA $1, $-2 0x017a 00378 (range_clause.go:9) CMPL runtime.writeBarrier(SB), $0 0x0181 00385 (range_clause.go:9) JEQ 389 0x0183 00387 (range_clause.go:9) JMP 439 0x0185 00389 (range_clause.go:9) MOVQ AX, (BX)(DX*8) 0x0189 00393 (range_clause.go:9) JMP 395 0x018b 00395 (range_clause.go:9) PCDATA $0, $0 0x018b 00395 (range_clause.go:9) PCDATA $1, $6 0x018b 00395 (range_clause.go:9) MOVQ BX, "".newArr+216(SP) 0x0193 00403 (range_clause.go:9) MOVQ SI, "".newArr+224(SP) 0x019b 00411 (range_clause.go:9) MOVQ CX, "".newArr+232(SP) 0x01a3 00419 (range_clause.go:9) JMP 421 0x01a5 00421 (range_clause.go:8) MOVQ ""..autotmp_10+112(SP), CX 0x01aa 00426 (range_clause.go:8) INCQ CX 0x01ad 00429 (range_clause.go:8) MOVQ CX, ""..autotmp_10+112(SP) 0x01b2 00434 (range_clause.go:8) JMP 260 0x01b7 00439 (range_clause.go:9) PCDATA $0, $-2 0x01b7 00439 (range_clause.go:9) PCDATA $1, $-2 0x01b7 00439 (range_clause.go:9) CALL runtime.gcWriteBarrier(SB) 0x01bc 00444 (range_clause.go:9) JMP 395 0x01be 00446 (range_clause.go:9) PCDATA $0, $4 0x01be 00446 (range_clause.go:9) PCDATA $1, $8 0x01be 00446 (range_clause.go:9) MOVQ DX, ""..autotmp_21+64(SP) 0x01c3 00451 (range_clause.go:9) PCDATA $0, $5 0x01c3 00451 (range_clause.go:9) LEAQ type.*int(SB), AX 0x01ca 00458 (range_clause.go:9) PCDATA $0, $4 0x01ca 00458 (range_clause.go:9) MOVQ AX, (SP) 0x01ce 00462 (range_clause.go:9) PCDATA $0, $0 0x01ce 00462 (range_clause.go:9) MOVQ BX, 8(SP) 0x01d3 00467 (range_clause.go:9) MOVQ DX, 16(SP) 0x01d8 00472 (range_clause.go:9) MOVQ CX, 24(SP) 0x01dd 00477 (range_clause.go:9) MOVQ SI, 32(SP) 0x01e2 00482 (range_clause.go:9) CALL runtime.growslice(SB) 0x01e7 00487 (range_clause.go:9) PCDATA $0, $4 0x01e7 00487 (range_clause.go:9) MOVQ 40(SP), BX 0x01ec 00492 (range_clause.go:9) MOVQ 48(SP), AX 0x01f1 00497 (range_clause.go:9) MOVQ 56(SP), CX 0x01f6 00502 (range_clause.go:9) LEAQ 1(AX), SI 0x01fa 00506 (range_clause.go:9) MOVQ ""..autotmp_21+64(SP), DX 0x01ff 00511 (range_clause.go:9) JMP 366 0x0204 00516 (range_clause.go:11) PCDATA $0, $0 0x0204 00516 (range_clause.go:11) PCDATA $1, $10 0x0204 00516 (range_clause.go:11) MOVQ "".newArr+232(SP), AX 0x020c 00524 (range_clause.go:11) MOVQ "".newArr+224(SP), CX 0x0214 00532 (range_clause.go:11) PCDATA $0, $2 0x0214 00532 (range_clause.go:11) PCDATA $1, $0 0x0214 00532 (range_clause.go:11) MOVQ "".newArr+216(SP), DX 0x021c 00540 (range_clause.go:11) PCDATA $0, $0 0x021c 00540 (range_clause.go:11) PCDATA $1, $11 0x021c 00540 (range_clause.go:11) MOVQ DX, ""..autotmp_6+264(SP) 0x0224 00548 (range_clause.go:11) MOVQ CX, ""..autotmp_6+272(SP) 0x022c 00556 (range_clause.go:11) MOVQ AX, ""..autotmp_6+280(SP) 0x0234 00564 (range_clause.go:11) MOVQ $0, ""..autotmp_14+88(SP) 0x023d 00573 (range_clause.go:11) MOVQ ""..autotmp_6+272(SP), AX 0x0245 00581 (range_clause.go:11) MOVQ AX, ""..autotmp_15+80(SP) 0x024a 00586 (range_clause.go:11) JMP 588 0x024c 00588 (range_clause.go:11) MOVQ ""..autotmp_15+80(SP), AX 0x0251 00593 (range_clause.go:11) CMPQ ""..autotmp_14+88(SP), AX 0x0256 00598 (range_clause.go:11) JLT 605 0x0258 00600 (range_clause.go:11) JMP 816 0x025d 00605 (range_clause.go:11) MOVQ ""..autotmp_14+88(SP), AX 0x0262 00610 (range_clause.go:11) SHLQ $3, AX 0x0266 00614 (range_clause.go:11) PCDATA $0, $1 0x0266 00614 (range_clause.go:11) ADDQ ""..autotmp_6+264(SP), AX 0x026e 00622 (range_clause.go:11) MOVQ (AX), AX 0x0271 00625 (range_clause.go:11) MOVQ AX, ""..autotmp_16+176(SP) 0x0279 00633 (range_clause.go:11) MOVQ AX, "".v+144(SP) 0x0281 00641 (range_clause.go:12) TESTB AL, (AX) 0x0283 00643 (range_clause.go:12) PCDATA $0, $0 0x0283 00643 (range_clause.go:12) MOVQ (AX), AX 0x0286 00646 (range_clause.go:12) MOVQ AX, ""..autotmp_17+72(SP) 0x028b 00651 (range_clause.go:12) MOVQ AX, (SP) 0x028f 00655 (range_clause.go:12) CALL runtime.convT64(SB) 0x0294 00660 (range_clause.go:12) PCDATA $0, $1 0x0294 00660 (range_clause.go:12) MOVQ 8(SP), AX 0x0299 00665 (range_clause.go:12) PCDATA $0, $0 0x0299 00665 (range_clause.go:12) PCDATA $1, $12 0x0299 00665 (range_clause.go:12) MOVQ AX, ""..autotmp_18+168(SP) 0x02a1 00673 (range_clause.go:12) PCDATA $1, $13 0x02a1 00673 (range_clause.go:12) XORPS X0, X0 0x02a4 00676 (range_clause.go:12) MOVUPS X0, ""..autotmp_7+200(SP) 0x02ac 00684 (range_clause.go:12) PCDATA $0, $1 0x02ac 00684 (range_clause.go:12) PCDATA $1, $12 0x02ac 00684 (range_clause.go:12) LEAQ ""..autotmp_7+200(SP), AX 0x02b4 00692 (range_clause.go:12) MOVQ AX, ""..autotmp_20+160(SP) 0x02bc 00700 (range_clause.go:12) TESTB AL, (AX) 0x02be 00702 (range_clause.go:12) PCDATA $0, $7 0x02be 00702 (range_clause.go:12) PCDATA $1, $11 0x02be 00702 (range_clause.go:12) MOVQ ""..autotmp_18+168(SP), CX 0x02c6 00710 (range_clause.go:12) PCDATA $0, $8 0x02c6 00710 (range_clause.go:12) LEAQ type.int(SB), DX 0x02cd 00717 (range_clause.go:12) PCDATA $0, $7 0x02cd 00717 (range_clause.go:12) MOVQ DX, ""..autotmp_7+200(SP) 0x02d5 00725 (range_clause.go:12) PCDATA $0, $1 0x02d5 00725 (range_clause.go:12) MOVQ CX, ""..autotmp_7+208(SP) 0x02dd 00733 (range_clause.go:12) TESTB AL, (AX) 0x02df 00735 (range_clause.go:12) JMP 737 0x02e1 00737 (range_clause.go:12) MOVQ AX, ""..autotmp_19+312(SP) 0x02e9 00745 (range_clause.go:12) MOVQ $1, ""..autotmp_19+320(SP) 0x02f5 00757 (range_clause.go:12) MOVQ $1, ""..autotmp_19+328(SP) 0x0301 00769 (range_clause.go:12) PCDATA $0, $0 0x0301 00769 (range_clause.go:12) MOVQ AX, (SP) 0x0305 00773 (range_clause.go:12) MOVQ $1, 8(SP) 0x030e 00782 (range_clause.go:12) MOVQ $1, 16(SP) 0x0317 00791 (range_clause.go:12) CALL fmt.Println(SB) 0x031c 00796 (range_clause.go:12) JMP 798 0x031e 00798 (range_clause.go:11) MOVQ ""..autotmp_14+88(SP), AX 0x0323 00803 (range_clause.go:11) INCQ AX 0x0326 00806 (range_clause.go:11) MOVQ AX, ""..autotmp_14+88(SP) 0x032b 00811 (range_clause.go:11) JMP 588 0x0330 00816 (<unknown line number>) PCDATA $1, $0 0x0330 00816 (<unknown line number>) MOVQ 336(SP), BP 0x0338 00824 (<unknown line number>) ADDQ $344, SP 0x033f 00831 (<unknown line number>) RET 0x0340 00832 (<unknown line number>) NOP 0x0340 00832 (range_clause.go:5) PCDATA $1, $-1 0x0340 00832 (range_clause.go:5) PCDATA $0, $-1 0x0340 00832 (range_clause.go:5) CALL runtime.morestack_noctxt(SB) 0x0345 00837 (range_clause.go:5) JMP 0
看着輸出結果,很cool~~~但是看不懂:(
彙編的簡單知識
go使用的彙編叫做 plan9彙編
。最初go是在plan9系統上開發的,後來纔在Linux和Mac上實現。
關於plan9彙編的入門,推薦看這個視頻: plan9彙編入門|go夜讀
寄存器
寄存器是CPU內部用來存放數據的一些小型存儲區域,用來暫時存放參與運算的數據和運算結果。
助記符 | 名字 | 用途 |
---|---|---|
AX | 累加寄存器(AccumulatorRegister) | 用於存放數據,包括算術、操作數、結果和臨時存放地址 |
BX | 基址寄存器(BaseRegister) | 用於存放訪問存儲器時的地址 |
CX | 計數寄存器(CountRegister) | 用於保存計算值,用作計數器 |
DX | 數據寄存器(DataRegister) | 用於數據傳遞,在寄存器間接尋址中的I/O指令中存放I/O端口的地址 |
SP | 堆棧頂指針(StackPointer) |
如果是 symbol+offset(SP)
的形式表示go彙編的僞寄存器;如果是 offset(SP)
的形式表示硬件寄存器 |
BP | 堆棧基指針(BasePointer) | 保存在進入函數前的棧頂基址 |
SB | 靜態基指針(StaticBasePointer) |
go彙編的僞寄存器。 foo(SB)
用於表示變量在內存中的地址, foo+4(SB)
表示foo起始地址往後偏移四字節。一般用來聲明函數或全局變量 |
FP | 棧幀指針(FramePointer) |
go彙編的僞寄存器。引用函數的輸入參數,形式是 symbol+offset(FP)
,例如 arg0+0(FP)
|
SI | 源變址寄存器(SourceIndex) | 用於存放源操作數的偏移地址 |
DI | 目的寄存器(DestinationIndex) | 用於存放目的操作數的偏移地址 |
操作指令
用於指導彙編如何進行。以下指令後綴<mark>Q</mark>說明是64位上的彙編指令。
助記符 | 指令種類 | 用途 | 示例 |
---|---|---|---|
MOVQ | 傳送 | 數據傳送 |
MOVQ 48, AX
表示把48傳送AX中 |
LEAQ | 傳送 | 地址傳送 |
LEAQ AX, BX
表示把AX有效地址傳送到BX中 |
|
|
|
PUSHQ AX
表示先修改棧頂指針,將AX內容送入新的棧頂位置 SUBQ
代替 |
|
|
|
POPQ AX
表示先彈出棧頂的數據,然後修改棧頂指針 ADDQ
代替 |
ADDQ | 運算 | 相加並賦值 |
ADDQ BX, AX
表示BX和AX的值相加並賦值給AX |
SUBQ | 運算 | 相減並賦值 | 略,同上 |
IMULQ | 運算 | 無符號乘法 | 略,同上 |
IDIVQ | 運算 | 無符號除法 |
IDIVQ CX
除數是CX,被除數是AX,結果存儲到AX中 |
CMPQ | 運算 | 對兩數相減,比較大小 |
CMPQ SI CX
表示比較SI和CX的大小。與SUBQ類似,只是不返回相減的結果 |
CALL | 轉移 | 調用函數 |
CALL runtime.printnl(SB)
表示通過<mark>printnl</mark>函數的內存地址發起調用 |
JMP | 轉移 | 無條件轉移指令 |
JMP 389
無條件轉至 0x0185
地址處(十進制389轉換成十六進制0x0185) |
JLS | 轉移 | 條件轉移指令 |
JLS 389
上一行的比較結果,左邊小於右邊則執行跳到 0x0185
地址處(十進制389轉換成十六進制0x0185) |
可以看到,表中的 PUSHQ
和 POPQ
被去掉了,這是因爲在go彙編中,對棧的操作並不是出棧入棧,而是通過對SP進行運算來實現的。
標誌位
助記符 | 名字 | 用途 |
---|---|---|
OF | 溢出 | 0爲無溢出 1爲溢出 |
CF | 進位 | 0爲最高位無進位或錯位 1爲有 |
PF | 奇偶 | 0表示數據最低8位中1的個數爲奇數,1則表示1的個數爲偶數 |
AF | 輔助進位 | |
ZF | 零 | 0表示結果不爲0 1表示結果爲0 |
SF | 符號 | 0表示最高位爲0 1表示最高位爲1 |
這麼一通信息轟炸下來,作爲初學者可能已經頭暈腦脹記不住了,其實是否記住這並不重要——後面分析用到了再回來查閱意思即可。
分析彙編代碼
從1+1開始
“好了,現在我們已經學會了加減乘除四則運算,接下來我們來解答一下這道微積分的題目”XD
下面內容查看 原文>> yuchanns'Atelier