Golang 汇编杂项
Contents
查看Go源码输出的汇编代码
go tool compile -S hello.go
或
go tool objdump -s "main\.main" hello
或
go tool compile -N -S hello.go
最好用这个,这种是禁止优化的
小例子
只有一个返回值
[22:58:38] emacsist:Desktop $ cat hello.go
package main
func main() {
a, b := 3, 4
println(add(a, b))
}
func add(a, b int) int {
return a + b
}
输出的汇编代码:
"".add t=1 size=19 args=0x18 locals=0x0
0x0000 00000 (hello.go:8) TEXT "".add(SB), $0-24
0x0000 00000 (hello.go:8) FUNCDATA $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
0x0000 00000 (hello.go:8) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (hello.go:9) MOVQ "".b+16(FP), AX
0x0005 00005 (hello.go:9) MOVQ "".a+8(FP), CX
0x000a 00010 (hello.go:9) ADDQ CX, AX
0x000d 00013 (hello.go:9) MOVQ AX, "".~r2+24(FP)
0x0012 00018 (hello.go:9) RET
0x0000 48 8b 44 24 10 48 8b 4c 24 08 48 01 c8 48 89 44 H.D$.H.L$.H..H.D
0x0010 24 18 c3 $..
FP => 指向栈底(高位内存地址)(Frame pointer) SP => 指向栈顶(低位内存地址)(Stack pointer)
- ”“.add => 表示命名空间,这就是上面源码中的 add 函数
- t => 这个我也不知道表示什么意思。。。有知道的,麻烦告知一下 ^_^
- size => 表示这个函数是 19 个字节
- args => 表示参数总占字节数(16进制显示)
- locals => 表示局部变量(即在函数内部的变量)的总字节数(16进制显示)
- TEXT “”.add(SB), $0-24 => 这表示代码段(TEXT),SB(Static Base)表示函数 add 的地址,$0:表示局部变量总字节数(十进制显示), -24:表示参数总占字节数(十进制显示)(包括传入和返回参数的总字节数之和),因为这里一共3个 int, 3*8=24 . 可以这样子输出它的大小:
fmt.Printf("a: %T, %d\n", a, unsafe.Sizeof(a))
,我这里输出为:a: int, 8
- FUNCDATA => 这个是与垃圾收集器相关的,这里不理它
- MOVQ “”.b+16(FP), AX => MOVQ表示移动2个字(1字=2字节=16位,所以这里是32位,可以用一个32位的寄存器来保存,这里为AX)。即 FP + 16 (即是变量b的内存数据), FP + 8 (即是变量a的内存数据)。从这里可以推出,Golang中处理参数是从左到右顺序压入栈的。
- ADDQ CX, AX => 前面已经将变量b的数据放到了 AX, 变量a的数据放到了CX, 这里就将它们相加,然后再保存到 AX 寄存器中。
- MOVQ AX, “”.~r2+24(FP) => 将返回结果(结果保存到了AX)保存到栈的 FP+24 的位置。~r2 表示参数的位置(位置是按从函数左到右的参数顺序开始算起,包括返回参数。 ~r2 表示第2<位置从0开始> 个参数的值为 AX的值。从这里可以推出:返回值的存储地址比参数的存储地址高。
- RET => 函数返回
多个返回值
package main
func main() {
a, b, c := 3, 4, 5
a1, s2 := add(a, b, c)
println(a1, s2)
}
func add(a, b, c int) (int, int) {
return a + b + c, a + b
}
汇编后的代码
"".add t=1 size=32 args=0x28 locals=0x0
0x0000 00000 (hello.go:9) TEXT "".add(SB), $0-40
0x0000 00000 (hello.go:9) FUNCDATA $0, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
0x0000 00000 (hello.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (hello.go:10) MOVQ "".b+16(FP), AX
0x0005 00005 (hello.go:10) MOVQ "".a+8(FP), CX
0x000a 00010 (hello.go:10) ADDQ CX, AX
0x000d 00013 (hello.go:10) MOVQ "".c+24(FP), CX
0x0012 00018 (hello.go:10) ADDQ AX, CX
0x0015 00021 (hello.go:10) MOVQ CX, "".~r3+32(FP)
0x001a 00026 (hello.go:10) MOVQ AX, "".~r4+40(FP)
0x001f 00031 (hello.go:10) RET
0x0000 48 8b 44 24 10 48 8b 4c 24 08 48 01 c8 48 8b 4c H.D$.H.L$.H..H.L
0x0010 24 18 48 01 c1 48 89 4c 24 20 48 89 44 24 28 c3 $.H..H.L$ H.D$(.
意思和上面的说明大概相同。这里特别说明的是:
~r3 => 表示第3个参数的值(即返回值)(未命名的情况下) ~r4 => 表示第4个参数的值(即返回值)(未命名的情况下)
如果返回值有命名的话,它是这样子的:
func add(a, b, c int) (d int, e int) {
return a + b + c, a + b
}
"".add t=1 size=32 args=0x28 locals=0x0
0x0000 00000 (hello.go:9) TEXT "".add(SB), $0-40
0x0000 00000 (hello.go:9) FUNCDATA $0, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
0x0000 00000 (hello.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (hello.go:10) MOVQ "".b+16(FP), AX
0x0005 00005 (hello.go:10) MOVQ "".a+8(FP), CX
0x000a 00010 (hello.go:10) ADDQ CX, AX
0x000d 00013 (hello.go:10) MOVQ "".c+24(FP), CX
0x0012 00018 (hello.go:10) ADDQ AX, CX
0x0015 00021 (hello.go:10) MOVQ CX, "".d+32(FP)
0x001a 00026 (hello.go:10) MOVQ AX, "".e+40(FP)
0x001f 00031 (hello.go:10) RET
0x0000 48 8b 44 24 10 48 8b 4c 24 08 48 01 c8 48 8b 4c H.D$.H.L$.H..H.L
0x0010 24 18 48 01 c1 48 89 4c 24 20 48 89 44 24 28 c3 $.H..H.L$ H.D$(.
所以,Go 中多个返回值的实现就是这样子处理的。
函数调用图
func add(a, b, c int) (d int, e int) {
var a1 int = 3
var a2 int = a1 * 3
return a + b + c + a2, a + b + a1
}
栈的改变
SP=SP-N,表示是开辟一个空间(进行函数调用)
SP=SP+N,表示释放一个空间 (函数调用完毕,返回)
低位地址
^
|
| add 函数局部变量2(a2) <------SP
| add 函数局部变量1(a1)
| main.main 的返回地址 <------FP
| add 函数参数1(a)
| add 函数参数2(b)
| add 函数参数3(c)
| add 函数参数返回值1(d)
| add 函数参数返回值2(e)
高位地址 runtime.main函数返回地址
FP的空间(即所有函数参数和返回值的空间):它是在调用方进行分配的(即,传入和返回参数的空间,在这个例子里是在 main 函数中分配好的,它属于 main 所在栈的一部分)
SP的空间:这个才是 add 的栈
在Go中,每一个 Goroutine 都有它自己的栈空间。
下面是禁用优化后输出的汇编:
"".add t=1 size=151 args=0x28 locals=0x28
0x0000 00000 (hello.go:9) TEXT "".add(SB), $40-40
0x0000 00000 (hello.go:9) SUBQ $40, SP
0x0004 00004 (hello.go:9) MOVQ BP, 32(SP)
0x0009 00009 (hello.go:9) LEAQ 32(SP), BP
0x000e 00014 (hello.go:9) FUNCDATA $0, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
0x000e 00014 (hello.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (hello.go:9) MOVQ $0, "".d+72(FP)
0x0017 00023 (hello.go:9) MOVQ $0, "".e+80(FP)
0x0020 00032 (hello.go:9) MOVQ $0, "".e+80(FP)
0x0029 00041 (hello.go:9) MOVQ $0, "".d+72(FP)
0x0032 00050 (hello.go:10) MOVQ $3, "".a1+8(SP)
0x003b 00059 (hello.go:11) MOVQ $9, "".a2(SP)
0x0043 00067 (hello.go:12) MOVQ "".a+48(FP), AX
0x0048 00072 (hello.go:12) MOVQ "".b+56(FP), CX
0x004d 00077 (hello.go:12) MOVQ "".c+64(FP), DX
0x0052 00082 (hello.go:12) ADDQ CX, AX
0x0055 00085 (hello.go:12) LEAQ 9(AX)(DX*1), AX
0x005a 00090 (hello.go:12) MOVQ AX, ""..autotmp_3+24(SP)
0x005f 00095 (hello.go:12) MOVQ "".a+48(FP), AX
0x0064 00100 (hello.go:12) MOVQ "".b+56(FP), CX
0x0069 00105 (hello.go:12) MOVQ "".a1+8(SP), DX
0x006e 00110 (hello.go:12) ADDQ CX, AX
0x0071 00113 (hello.go:12) ADDQ DX, AX
0x0074 00116 (hello.go:12) MOVQ AX, ""..autotmp_4+16(SP)
0x0079 00121 (hello.go:12) MOVQ ""..autotmp_3+24(SP), AX
0x007e 00126 (hello.go:12) MOVQ AX, "".d+72(FP)
0x0083 00131 (hello.go:12) MOVQ ""..autotmp_4+16(SP), AX
0x0088 00136 (hello.go:12) MOVQ AX, "".e+80(FP)
0x008d 00141 (hello.go:12) MOVQ 32(SP), BP
0x0092 00146 (hello.go:12) ADDQ $40, SP
0x0096 00150 (hello.go:12) RET
返回值设置零值:
0x000e 00014 (hello.go:9) MOVQ $0, "".d+72(FP)
0x0017 00023 (hello.go:9) MOVQ $0, "".e+80(FP)
0x0020 00032 (hello.go:9) MOVQ $0, "".e+80(FP)
0x0029 00041 (hello.go:9) MOVQ $0, "".d+72(FP)