1.1 调试源代码

各位读者朋友,很高兴大家通过本博客学习 Go 语言,感谢一路相伴!《Go语言设计与实现》的纸质版图书已经上架京东,有需要的朋友请点击 链接 购买。

Go 语言作为开源项目,我们可以很轻松地获取它的源代码,它有着非常复杂的项目结构和庞大的代码库,今天的 Go 语言中差不多有 150 万行源代码,其中包含将近 140 万行的 Go 语言代码,我们可以使用如下所示的命令查看项目中代码的行数:

$ cloc src
    5988 text files.
    5875 unique files.
    1165 files ignored.

github.com/AlDanial/cloc v 1.78  T=6.96 s (693.7 files/s, 274805.2 lines/s)
-----------------------------------------------------------------------------------
Language                         files          blank        comment           code
-----------------------------------------------------------------------------------
Go                                4199         139910         221375        1398357
Assembly                           486          12784          19137         106699
C                                   64            718            562           4587
JSON                                12              0              0           1712
...
-----------------------------------------------------------------------------------
SUM:                              4828         154344         242395        1515787
-----------------------------------------------------------------------------------

随着 Go 语言的不断演进,整个代码库也会随着时间不断变化,所以上面的统计结果每天都有所不同。虽然该项目有着巨大的代码库,但是想要调试 Go 语言并不是不可能的,只要我们掌握合适的方法并且对 Go 语言的标准库有一些了解,就可以调试 Go 语言,我们在这里会介绍一些编译和调试 Go 语言的方法。

1.1.1 编译源码 #

fmt.Printlnprintln
func Println(a ...interface{}) (n int, err error) {
	println("draven")
	return Fprintln(os.Stdout, a...)
}

当我们修改了 Go 语言的源代码项目,可以使用仓库中提供的脚本来编译生成 Go 语言的二进制以及相关的工具链:

$ ./src/make.bash
Building Go cmd/dist using /usr/local/Cellar/go/1.14.2_1/libexec. (go1.14.2 darwin/amd64)
Building Go toolchain1 using /usr/local/Cellar/go/1.14.2_1/libexec.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for darwin/amd64.
---
Installed Go for darwin/amd64 in /Users/draveness/go/src/github.com/golang/go
Installed commands in /Users/draveness/go/src/github.com/golang/go/bin
./src/make.bash$GOPATH/src/github.com/golang/go/bin
$ cat main.go
package main

import "fmt"

func main() {
	fmt.Println("Hello World")
}
$ $GOPATH/src/github.com/golang/go/bin/go run main.go
draven
Hello World
fmt.Printlngo run main.gogo

1.1.2 中间代码 #

Go 语言的应用程序在运行之前需要先编译成二进制,在编译的过程中会经过中间代码生成阶段,Go 语言编译器的中间代码具有静态单赋值(Static Single Assignment、SSA)的特性,我们会在后面介绍该中间代码的该特性,在这里我们只需要知道这是一种中间代码的表示方式。

很多 Go 语言的开发者都知道我们可以使用下面的命令将 Go 语言的源代码编译成汇编语言,然后通过汇编语言分析程序具体的执行过程:

$ go build -gcflags -S main.go
	rel 22+4 t=8 os.(*file).close+0
"".main STEXT size=137 args=0x0 locals=0x58
	0x0000 00000 (main.go:5)	TEXT	"".main(SB), ABIInternal, $88-0
	0x0000 00000 (main.go:5)	MOVQ	(TLS), CX
	0x0009 00009 (main.go:5)	CMPQ	SP, 16(CX)
	...
	rel 5+4 t=17 TLS+0
	rel 40+4 t=16 type.string+0
	rel 52+4 t=16 ""..stmp_0+0
	rel 64+4 t=16 os.Stdout+0
	rel 71+4 t=16 go.itab.*os.File,io.Writer+0
	rel 113+4 t=8 fmt.Fprintln+0
	rel 128+4 t=8 runtime.morestack_noctxt+0

然而上述的汇编代码只是 Go 语言编译的结果,作为使用 Go 语言的开发者,我们已经能够通过上述结果分析程序的性能瓶颈,但是如果想要了解 Go 语言更详细的编译过程,我们可以通过下面的命令获取汇编指令的优化过程:

$ GOSSAFUNC=main go build main.go
# runtime
dumped SSA to /usr/local/Cellar/go/1.14.2_1/libexec/src/runtime/ssa.html
# command-line-arguments
dumped SSA to ./ssa.html
ssa.html

图 1 - 1 SSA 示例

上述 HTML 文件是可以交互的,当我们点击网页上的汇编指令时,页面会使用相同的颜色在 SSA 中间代码生成的不同阶段标识出相关的代码行,更方便开发者分析编译优化的过程。

1.1.3 小结 #

println
下一节