1.1 调试源代码
1.1.1 安装或者更新golang
1.这里以centos 为例,如果需要更新golang,一般而言只需要执行删除命令将已安装的golang 删除即可。
echo $GOROOT
rm -rf $GOROOT
2.下载新版的go
wget https://dl.google.com/go/go1.19.linux-amd64.tar.gz
3.解压到usr/local下
tar -C /usr/local/ -zxvf go1.19.linux-amd64.tar.gz
4.修改配置文件
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
5.执行使配置文件生效
source ~/.bashrc
#有可能不是显示最新版本,需要将旧版本go可执行文件替换掉
cp -f $GOROOT/bin/go* /usr/bin/
1.1.2 编译源码
我们可以使用如下所示的命令查看项目中代码的行数:

假设我们想要修改 Go 语言中常用方法 fmt.Println 的实现,实现如下所示的功能:在打印字符串之前先打印任意其它字符串。我们可以将该方法的实现修改成如下所示的代码片段,其中 println 是 Go 语言运行时提供的内置方法,它不需要依赖任何包就可以向标准输出打印字符串:
func Println(a ...any) (n int, err error) {
println("hope")
return Fprintln(os.Stdout, a...)
}
调试过程中遇到的一些问题,因为我使用的是go1.19版本,会出现以下错误:
[tdcode2@sz-sh-virt-tdcode2-17-74 src]$ ./make.bash
ERROR: Cannot find /home/tdcode2/go1.4/bin/go.
Set $GOROOT_BOOTSTRAP to a working Go tree >= Go 1.4.
这是由于go 1.5版以后的编译安装需要1.4版本go,所以如果想要通过源码方式安装高版本go,必须先安装好1.4版本的go。
git clone https://github.com/golang/go.git
cd go
git branch
git checkout release-branch.go1.4
cd src/
./all.bash
cp -R * /home/tdcode2/go1.4/
编译好,然后将其复制到对应的文件夹里面即可,就可以编译成功了:
[tdcode2@sz-sh-virt-tdcode2-17-74 src]$ sudo ./make.bash
[sudo] password for tdcode2:
Building Go cmd/dist using /root/go1.4. (go1.4-bootstrap-20170531 linux/amd64)
Building Go toolchain1 using /root/go1.4.
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 linux/amd64.
---
Installed Go for linux/amd64 in /usr/local/go
Installed commands in /usr/local/go/bin
当我们修改了 Go 语言的源代码项目,可以使用仓库中提供的脚本来编译生成 Go 语言的二进制以及相关的工具链:
之后,我们再调用 Println :
package main
import "fmt"
func main() {
fmt.Println("hello golang")
}
[tdcode2@sz-sh-virt-tdcode2-17-74 gotest]$ go run main.go
hope
hello golang
我们会发现上述命令成功地调用了我们修改后的 fmt.Println函数。
1.1.2 中间代码
Go 语言的应用程序在运行之前需要先编译成二进制,在编译的过程中会经过中间代码生成阶段,Go 语言编译器的中间代码具有静态单赋值(Static Single Assignment、SSA)的特性,我们会在后面介绍该中间代码的该特性,在这里我们只需要知道这是一种中间代码的表示方式。
很多 Go 语言的开发者都知道我们(其实我并不知道( ╯□╰ ))可以使用下面的命令将 Go 语言的源代码编译成汇编语言,然后通过汇编语言分析程序具体的执行过程:
[tdcode2@sz-sh-virt-tdcode2-17-74 gotest]$ go build -gcflags -S main.go
# command-line-arguments
main.main STEXT size=103 args=0x0 locals=0x40 funcid=0x0 align=0x0
0x0000 00000 (/home/tdcode2/hope/gotest/main.go:4) TEXT main.main(SB), ABIInternal, $64-0
0x0000 00000 (/home/tdcode2/hope/gotest/main.go:4) CMPQ SP, 16(R14)
0x0004 00004 (/home/tdcode2/hope/gotest/main.go:4) PCDATA $0, $-2
0x0004 00004 (/home/tdcode2/hope/gotest/main.go:4) JLS 92
0x0006 00006 (/home/tdcode2/hope/gotest/main.go:4) PCDATA $0, $-1
0x0006 00006 (/home/tdcode2/hope/gotest/main.go:4) SUBQ $64, SP
...
0x0060 e8 00 00 00 00 eb 99 .......
rel 2+0 t=23 type.string+0
rel 2+0 t=23 type.*os.File+0
rel 29+4 t=14 type.string+0
rel 41+4 t=14 main..stmp_0+0
rel 53+4 t=14 os.Stdout+0
rel 60+4 t=14 go.itab.*os.File,io.Writer+0
rel 78+4 t=7 fmt.Fprintln+0
rel 97+4 t=7 runtime.morestack_noctxt+0
go.cuinfo.producer.main SDWARFCUINFO dupok size=0
0x0000 72 65 67 61 62 69 regabi
go.cuinfo.packagename.main SDWARFCUINFO dupok size=0
0x0000 6d 61 69 6e main
go.info.fmt.Println$abstract SDWARFABSFCN dupok size=42
0x0000 05 66 6d 74 2e 50 72 69 6e 74 6c 6e 00 01 01 13 .fmt.Println....
0x0010 61 00 00 00 00 00 00 13 6e 00 01 00 00 00 00 13 a.......n.......
0x0020 65 72 72 00 01 00 00 00 00 00 err.......
rel 0+0 t=22 type.[]interface {}+0
rel 0+0 t=22 type.error+0
rel 0+0 t=22 type.int+0
rel 19+4 t=31 go.info.[]interface {}+0
rel 27+4 t=31 go.info.int+0
rel 37+4 t=31 go.info.error+0
main..inittask SNOPTRDATA size=32
...
然而上述的汇编代码只是 Go 语言编译的结果,作为使用 Go 语言的开发者,我们已经能够通过上述结果分析程序的性能瓶颈,但是如果想要了解 Go 语言更详细的编译过程,我们可以通过下面的命令获取汇编指令的优化过程:
[tdcode2@sz-sh-virt-tdcode2-17-74 gotest]$ GOSSAFUNC=main go build main.go
# runtime
dumped SSA to /home/tdcode2/hope/gotest/ssa.html
# command-line-arguments
dumped SSA to ./ssa.html
上述命令会在当前文件夹下生成一个 ssa.html 文件,我们打开这个文件后就能看到汇编代码优化的每一个步骤:

上述 HTML 文件是可以交互的,当我们点击网页上的汇编指令时,页面会使用相同的颜色在 SSA 中间代码生成的不同阶段标识出相关的代码行,更方便开发者分析编译优化的过程。
1.1.3 小结
掌握调试和自定义 Go 语言二进制的方法可以帮助我们快速验证对 Go 语言内部实现的猜想,通过最简单粗暴的 println 函数可以调试 Go 语言的源码和标准库;而如果我们想要研究源代码的详细编译优化过程,可以使用上面提到的 SSA 中间代码深入研究 Go 语言的中间代码以及编译优化的方式,不过只要我们想了解 Go 语言的实现原理,阅读源代码是绕不开的过程