go 编译 ssa与Plan9

发布时间 2023-11-28 11:15:59作者: 杨阳的技术博客

一、目的

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