本文快速阅读需要一定的汇编、Go、编译原理基础
因水平极其有限,错误难以避免,欢迎批评指正
package main
func main() {
var a int64 = 10
var b int64 = 20
a += sum(a, b)
}
func sum(a int64, b int64) int64 {
return a + b
}
函数定义:TEXT 函数名(SB), [flags,] $栈大小[-参数及返回值大小]。再次注意,函数自己的参数及返回值不在自己的栈帧中。而自己栈帧大小包括调用别的函数的返回值及参数。flags一般很多,遇到时搜索一下啥意思
FUNCDATA和PCDATA:记录了函数中指针信息和调用信息等,panic时的调用情况及垃圾回收时的根对象都分别依赖它们。它们是编译器自行插入的,阅读时可以跳过
使用go tool compile -S / go tool objdump命令输出的汇编来说,所有的 SP 都是真SP即SP寄存器中的地址。所以从下面汇编(使用go tool compile -S -N -l)可以看出没有负索引取值
a+24(SP)和40(SP):前者代表a的起始地址在SP上方24字节位置。后者代表的地址为SP上方40字节处。
"".main STEXT size=88 args=0x0 locals=0x30 funcid=0x0
# main函数,ABIInternal代表使用了新的ABI,即不是所有参数都在栈中了,main函数栈帧占48字节
0x0000 00000 (main.go:3) TEXT "".main(SB), ABIInternal, $48-0
# 48可以计算出来,看完后再来理解一下:48 = 局部变量a,b sum参数及返回地址 上一个栈帧BP 一共6个8B即48
# 下面这几行是判断栈空间是否足够。不够进行栈扩容。同样的,GC时可以进行栈缩减
0x0000 00000 (main.go:3) CMPQ SP, 16(R14)
0x0004 00004 (main.go:3) PCDATA $0, $-2
0x0004 00004 (main.go:3) JLS 81
0x0006 00006 (main.go:3) PCDATA $0, $-1
# SP(栈顶)减少48,即为当前栈帧分配48字节。我们读代码时可以对称读,下面必定有个命令是加48
0x0006 00006 (main.go:3) SUBQ $48, SP
# 先保存上一个栈帧的栈底(上一栈帧的起始)
0x000a 00010 (main.go:3) MOVQ BP, 40(SP)
# BP移动到新的栈帧栈底。我们可以发现,其实没有使用FP,如果有FP的话FP的值会为48(SP)。没有FP原因上面也说了。我们需要注意的是不是任何时候FP和伪SP/BP的位置间隔都是一样的。
0x000f 00015 (main.go:3) LEAQ 40(SP), BP
0x0014 00020 (main.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0014 00020 (main.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
# 可以看出就算没有优化,也是没有定义再赋值,而是直接给a赋值10
0x0014 00020 (main.go:4) MOVQ $10, "".a+24(SP)
# 给b赋值20
0x001d 00029 (main.go:5) MOVQ $20, "".b+16(SP)
# sum参数之一放到AX寄存器中
0x0026 00038 (main.go:6) MOVQ "".a+24(SP), AX
# 第二个参数放到BX寄存器中
0x002b 00043 (main.go:6) MOVL $20, BX
0x0030 00048 (main.go:6) PCDATA $1, $0
# 调用sum函数。此时我们发现b下面还有16字节,其实是sum的调用参数
0x0030 00048 (main.go:6) CALL "".sum(SB)
# 返回结果存在寄存器AX中,这里存到栈中,可见在局部变量a上面
0x0035 00053 (main.go:6) MOVQ AX, ""..autotmp_2+32(SP)
# a值存在CX
0x003a 00058 (main.go:6) MOVQ "".a+24(SP), CX
# a与结果相加
0x003f 00063 (main.go:6) ADDQ AX, CX
# 相加结果赋值给a
0x0042 00066 (main.go:6) MOVQ CX, "".a+24(SP)
# BP变成上一个栈帧的栈底
0x0047 00071 (main.go:7) MOVQ 40(SP), BP
# 函数调用完成之前,SP回归上一栈帧栈顶
0x004c 00076 (main.go:7) ADDQ $48, SP
# 返回,
0x0050 00080 (main.go:7) RET
# 下面这几行对应上面栈扩容的跳转行。可以看见,栈扩容后又跳转回去重新判断栈是否有爆栈可能性
0x0051 00081 (main.go:7) NOP
0x0051 00081 (main.go:3) PCDATA $1, $-1
0x0051 00081 (main.go:3) PCDATA $0, $-2
0x0051 00081 (main.go:3) CALL runtime.morestack_noctxt(SB)
0x0056 00086 (main.go:3) PCDATA $0, $-1
0x0056 00086 (main.go:3) JMP 0
0x0000 49 3b 66 10 76 4b 48 83 ec 30 48 89 6c 24 28 48 I;f.vKH..0H.l$(H
0x0010 8d 6c 24 28 48 c7 44 24 18 0a 00 00 00 48 c7 44 .l$(H.D$.....H.D
0x0020 24 10 14 00 00 00 48 8b 44 24 18 bb 14 00 00 00 $.....H.D$......
0x0030 e8 00 00 00 00 48 89 44 24 20 48 8b 4c 24 18 48 .....H.D$ H.L$.H
0x0040 01 c1 48 89 4c 24 18 48 8b 6c 24 28 48 83 c4 30 ..H.L$.H.l$(H..0
0x0050 c3 e8 00 00 00 00 eb a8 ........
rel 49+4 t=7 "".sum+0
rel 82+4 t=7 runtime.morestack_noctxt+0
"".sum STEXT nosplit size=56 args=0x10 locals=0x10 funcid=0x0
# 可见sum的栈帧大小为16B,参数大小为16B,存在上一个栈帧
0x0000 00000 (main.go:9) TEXT "".sum(SB), NOSPLIT|ABIInternal, $16-16
# sum函数有NOSPLIT修饰,所以没有栈扩容阶段
0x0000 00000 (main.go:9) SUBQ $16, SP
0x0004 00004 (main.go:9) MOVQ BP, 8(SP)
0x0009 00009 (main.go:9) LEAQ 8(SP), BP
0x000e 00014 (main.go:9) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 (main.go:9) FUNCDATA $5, "".sum.arginfo1(SB)
# 这里注意一下,这里是把main的局部变量a存在AX寄存器中的值移动到了sum的参数a中。
# 而sum的参数a存在main栈帧中,所以可以看出加24。
# 一个偏移24一个偏移32。不是16和24的原因是,CALL和RET会进行隐式的PC/IP寄存器的值存储
0x000e 00014 (main.go:9) MOVQ AX, "".a+24(SP)
0x0013 00019 (main.go:9) MOVQ BX, "".b+32(SP)
# 这个应该是return a + b变成了 r2 = a + b; return r2。先把r2区域置0
0x0018 00024 (main.go:9) MOVQ $0, "".~r2(SP)
# 加法
0x0020 00032 (main.go:10) MOVQ "".a+24(SP), AX
0x0025 00037 (main.go:10) ADDQ "".b+32(SP), AX
0x002a 00042 (main.go:10) MOVQ AX, "".~r2(SP)
0x002e 00046 (main.go:10) MOVQ 8(SP), BP
0x0033 00051 (main.go:10) ADDQ $16, SP
0x0037 00055 (main.go:10) RET
0x0000 48 83 ec 10 48 89 6c 24 08 48 8d 6c 24 08 48 89 H...H.l$.H.l$.H.
0x0010 44 24 18 48 89 5c 24 20 48 c7 04 24 00 00 00 00 D$.H.\$ H..$....
0x0020 48 8b 44 24 18 48 03 44 24 20 48 89 04 24 48 8b H.D$.H.D$ H..$H.
0x0030 6c 24 08 48 83 c4 10 c3 l$.H....
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
0x0000 6d 61 69 6e main
""..inittask SNOPTRDATA size=24
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010 00 00 00 00 00 00 00 00 ........
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
0x0000 01 00 00 00 00 00 00 00 ........
"".sum.arginfo1 SRODATA static dupok size=5
0x0000 00 08 08 08 ff .....
------
celler BP (8 bytes)
------ main函数栈帧 BP
sum.ret (8 bytes)
------
main.a (8 bytes)
------
main.b (8 bytes)
------
sum.b (8 bytes)
------
sum.a (8 bytes)
------ main函数栈帧 SP
ret addr (8 bytes)
------
caller(main) BP (8 bytes)
------ sum函数栈帧 BP
临时变量 (8 bytes)
------ sum函数栈帧 SP