go 编译 ssa与Plan9
作者:@杨阳
本文为作者原创,转载请注明出处:https://www.cnblogs.com/studyios/p/17861324.html
一、目的
简单看下go编译过程,便于理解go为什么能编译出不同平台都能运行的可执行文件,克服了c和c++需要针对不同平台分开编译的问题。
那些过程能在开发过程中用到,帮助定位问题。
二、整体
编译前端的都好理解,语义分析时候,需要进行go的逃逸分析。
中间码生成 ssa
任意写一个demo:
import "fmt"
func main() {
fmt.Println("hello world")
c := Add(1, 2)
fmt.Println(c)
}
func Add(a, b int) int {
return a + b
}
通过命令行查看中间码生成过程:
export GOSSAFUNC=main
go build
$ go build
# runtime
dumped SSA to **/Desktop/demo1/ssa.html
# demo1
dumped SSA to ./ssa.html
打开这个 ssa.html 为了方便查看 对图片转换了方向。
最上面的源代码,然后AST一步步到了 生成ssa
AST截图:
ssa
SSA是三地址代码(Three-address Code)的一种变体,SSA要求代码中每个变量只能被赋值一次,并且任何变量在使用之前必须先申明。这是一种极简的代码形式,基于对变量的以上两点约束,编译器可以很安全地对代码进行各种操作及优化,例如如果一个变量被赋值后没有被使用,那么其赋值语句就是可以删掉的。
ssa 并不是 真正的汇编代码,不过很相似
能看到 调用了两次 print
v32 00016 (314) CALL fmt.Fprintln(SB)
v70 00032 (314) CALL fmt.Fprintln(SB)
对应上面的两次打印。
还有 调用了:00021 (+9) CALL runtime.convT64(SB)
func convT64(val uint64) (x unsafe.Pointer) {
if val < uint64(len(staticuint64s)) {
x = unsafe.Pointer(&staticuint64s[val])
} else {
x = mallocgc(8, uint64Type, false)
*(*uint64)(x) = val
}
return
}
因为代码中未指定是int32或者int64,给默认转为int64了
通过这个中间代码能大致看出 go的程序执行时候,最终调用了什么方法,在排查问题时候,也是多一个途径。
机器码 Plan9 汇编代码
对于Go,生成的目标代码在任何时候都是Plan 9汇编(屏蔽了操作系统带来的差异,如系统调用规范,而CPU带给Go汇编的主要差异就是寄存器数量和名字)。之后会再根据架构和操作系统翻译成对应的机器代码,Go在这个层面是平台无关性的。
过了这个阶段后,就会生成不同平台的机器码。
使用命令:
go build -gcflags -S main.go
go build -gcflags -S main.go
# command-line-arguments
command-line-arguments.main STEXT size=170 args=0x0 locals=0x50 funcid=0x0 align=0x0
0x0000 00000 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) TEXT command-line-arguments.main(SB), ABIInternal, $80-0
0x0000 00000 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) CMPQ SP, 16(R14)
0x0004 00004 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) PCDATA $0, $-2
0x0004 00004 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) JLS 156
0x000a 00010 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) PCDATA $0, $-1
0x000a 00010 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) PUSHQ BP
0x000b 00011 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) MOVQ SP, BP
0x000e 00014 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) SUBQ $72, SP
0x0012 00018 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) FUNCDATA $0, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
0x0012 00018 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) FUNCDATA $1, gclocals·WzcH5HabKQq0jVF7ifBBfA==(SB)
0x0012 00018 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) FUNCDATA $2, command-line-arguments.main.stkobj(SB)
0x0012 00018 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:7) MOVUPS X15, command-line-arguments..autotmp_17+56(SP)
0x0018 00024 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:7) LEAQ type:string(SB), DX
0x001f 00031 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:7) MOVQ DX, command-line-arguments..autotmp_17+56(SP)
0x0024 00036 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:7) LEAQ command-line-arguments..stmp_0(SB), DX
0x002b 00043 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:7) MOVQ DX, command-line-arguments..autotmp_17+64(SP)
0x0030 00048 (/usr/local/opt/go/libexec/src/fmt/print.go:314) MOVQ os.Stdout(SB), BX
0x0037 00055 (<unknown line number>) NOP
0x0037 00055 (/usr/local/opt/go/libexec/src/fmt/print.go:314) LEAQ go:itab.*os.File,io.Writer(SB), AX
0x003e 00062 (/usr/local/opt/go/libexec/src/fmt/print.go:314) LEAQ command-line-arguments..autotmp_17+56(SP), CX
0x0043 00067 (/usr/local/opt/go/libexec/src/fmt/print.go:314) MOVL $1, DI
0x0048 00072 (/usr/local/opt/go/libexec/src/fmt/print.go:314) MOVQ DI, SI
0x004b 00075 (/usr/local/opt/go/libexec/src/fmt/print.go:314) PCDATA $1, $0
0x004b 00075 (/usr/local/opt/go/libexec/src/fmt/print.go:314) CALL fmt.Fprintln(SB)
0x0050 00080 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:8) XCHGL AX, AX
0x0051 00081 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) MOVUPS X15, command-line-arguments..autotmp_19+40(SP)
0x0057 00087 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) MOVL $3, AX
0x005c 00092 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) PCDATA $1, $1
0x005c 00092 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) NOP
0x0060 00096 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) CALL runtime.convT64(SB)
0x0065 00101 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) LEAQ type:int(SB), DX
0x006c 00108 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) MOVQ DX, command-line-arguments..autotmp_19+40(SP)
0x0071 00113 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:9) MOVQ AX, command-line-arguments..autotmp_19+48(SP)
0x0076 00118 (/usr/local/opt/go/libexec/src/fmt/print.go:314) MOVQ os.Stdout(SB), BX
0x007d 00125 (<unknown line number>) NOP
0x007d 00125 (/usr/local/opt/go/libexec/src/fmt/print.go:314) LEAQ go:itab.*os.File,io.Writer(SB), AX
0x0084 00132 (/usr/local/opt/go/libexec/src/fmt/print.go:314) LEAQ command-line-arguments..autotmp_19+40(SP), CX
0x0089 00137 (/usr/local/opt/go/libexec/src/fmt/print.go:314) MOVL $1, DI
0x008e 00142 (/usr/local/opt/go/libexec/src/fmt/print.go:314) MOVQ DI, SI
0x0091 00145 (/usr/local/opt/go/libexec/src/fmt/print.go:314) PCDATA $1, $0
0x0091 00145 (/usr/local/opt/go/libexec/src/fmt/print.go:314) CALL fmt.Fprintln(SB)
0x0096 00150 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:10) ADDQ $72, SP
0x009a 00154 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:10) POPQ BP
0x009b 00155 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:10) RET
0x009c 00156 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:10) NOP
0x009c 00156 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) PCDATA $1, $-1
0x009c 00156 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) PCDATA $0, $-2
0x009c 00156 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) NOP
0x00a0 00160 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) CALL runtime.morestack_noctxt(SB)
0x00a5 00165 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) PCDATA $0, $-1
0x00a5 00165 (/Users/yangping/Desktop/golang_pratice/demo1/main.go:5) JMP 0
只截取了部分。
和ssa码基本能对应上。
总结:
整个编译过程流程非常多,逻辑非常复杂,这里只是稍稍探究了下。
除了常规的词法分析、语义分析外,go引入了 ssa和Plan9两种中间代码,这两个阶段能直接看到生成的源码,能方便我们定位一些问题。网上有很多教程 怎么阅读go的汇编代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!