原文地址: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
有疑问加站长微信联系(非本文作者)