1.1 调试源代码
https://draveness.me/golang/docs/part1-prerequisite/ch01-prepare/golang-debug/
https://github.com/golang/go
Go 语言作为开源项目,我们可以很轻松地获取它的源代码,它有着非常复杂的项目结构和庞大的代码库,今天的 Go 语言中差不多有 150 万行源代码,其中包含将近 140 万行的 Go 语言代码,我们可以使用如下所示的命令查看项目中代码的行数:
go1.18
$ sudo apt install cloc~/golang$ cloc src7472 text files.7184 unique files.1938 files ignored.9 errors:Line count, exceeded timeout: src/cmd/dist/build.goLine count, exceeded timeout: src/go/scanner/scanner_test.goLine count, exceeded timeout: src/net/http/requestwrite_test.goLine count, exceeded timeout: src/regexp/exec_test.goLine count, exceeded timeout: src/vendor/golang.org/x/net/idna/tables10.0.0.goLine count, exceeded timeout: src/vendor/golang.org/x/net/idna/tables11.0.0.goLine count, exceeded timeout: src/vendor/golang.org/x/net/idna/tables12.0.0.goLine count, exceeded timeout: src/vendor/golang.org/x/net/idna/tables13.0.0.goLine count, exceeded timeout: src/vendor/golang.org/x/net/idna/tables9.0.0.gogithub.com/AlDanial/cloc v 1.82 T=46.94 s (118.1 files/s, 45428.4 lines/s)-----------------------------------------------------------------------------------Language files blank comment code-----------------------------------------------------------------------------------Go 4882 159283 255970 1552203Assembly 496 13746 20181 114588C 76 807 622 4899JSON 12 0 0 1712Markdown 9 413 0 1459Bourne Shell 11 184 774 1352Perl 10 173 171 1109Bourne Again Shell 12 101 208 460Python 1 133 104 374DOS Batch 5 57 1 258C/C++ Header 12 71 202 230Windows Resource File 4 23 0 143RobotFramework 1 0 0 106C++ 1 8 9 17Objective C 1 2 3 11awk 1 1 6 7make 4 3 7 7Dockerfile 1 3 3 6MATLAB 1 1 0 4HTML 1 0 0 1CSS 1 0 0 1-----------------------------------------------------------------------------------SUM: 5542 175009 278261 1678947-----------------------------------------------------------------------------------
随着 Go 语言的不断演进,整个代码库也会随着时间不断变化,所以上面的统计结果每天都有所不同。虽然该项目有着巨大的代码库,但是想要调试 Go 语言并不是不可能的,只要我们掌握合适的方法并且对 Go 语言的标准库有一些了解,就可以调试 Go 语言,我们在这里会介绍一些编译和调试 Go 语言的方法。
编译源码
假设我们想要修改 Go 语言中常用方法 fmt.Println 的实现,实现如下所示的功能:在打印字符串之前先打印任意其它字符串。我们可以将该方法的实现修改成如下所示的代码片段,其中 println 是 Go 语言运行时提供的内置方法,它不需要依赖任何包就可以向标准输出打印字符串:
func Println(a ...interface{}) (n int, err error) {println("draven")return Fprintln(os.Stdout, a...)}
当我们修改了 Go 语言的源代码项目,可以使用仓库中提供的脚本来编译生成 Go 语言的二进制以及相关的工具链:
~/golang$ ./src/make.bashbash: ./src/make.bash: Permission denied~/golang$ chmod a+x ./src/make.bash~/golang$ ./src/make.bashmake.bash must be run from $GOROOT/src~/golang$ cd src~/golang/src$ ./make.bash
./make.bash 脚本会编译 Go 语言的二进制、工具链以及标准库和命令并将源代码和编译好的二进制文件移动到对应的位置上。如上述代码所示,编译好的二进制会存储在 $GOPATH/src/github.com/golang/go/bin 目录中,这里需要使用绝对路径来访问并使用它:
~/golang$ /bin/go run main.goHello World~/golang$ /home/ningluwsl/golang/bin/go run main.godravenHello World
我们会发现上述命令成功地调用了我们修改后的 fmt.Println 函数,而在这时如果直接使用 go run main.go,很可能会使用包管理器安装的 go 二进制,得不到期望的结果。
中间代码
Go 语言的应用程序在运行之前需要先编译成二进制,在编译的过程中会经过中间代码生成阶段,Go 语言编译器的中间代码具有静态单赋值(Static Single Assignment、SSA)的特性,我们会在后面介绍该中间代码的该特性,在这里我们只需要知道这是一种中间代码的表示方式。
很多 Go 语言的开发者都知道我们可以使用下面的命令将 Go 语言的源代码编译成汇编语言,然后通过汇编语言分析程序具体的执行过程:
