go 编译 ssa与Plan9

一、目的

简单看下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 () 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 () 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的汇编代码。

posted @ 2023-11-28 11:12  杨阳的技术博客  阅读(210)  评论(0)    收藏  举报