基本技巧
2021.07

1.3 基本技巧

研究Go的内部实现,这里介绍一些基本的技巧。

阅读源代码

Go语言的源代码布局是有一些规律的。假定读者在$GOROOT下:

- ./misc 一些工具
- ./src 源代码
- ./src/cmd 命令工具,包括6c, 6l, 6g等等。最后打包成go命令。
- ./src/pkg 各个package的源代码
- ./src/pkg/runtime Go的runtime包,本书分析的最主要的部分
-  AUTHORS — 文件,官方 Go语言作者列表
|– CONTRIBUTORS — 文件,第三方贡献者列表
|– LICENSE — 文件,Go语言发布授权协议
|– PATENTS — 文件,专利
|– README — 文件,README文件,大家懂的。提一下,经常有人说:Go官网打不开啊,怎么办?其实,在README中说到了这个。该文件还提到,如果通过二进制安装,需要设置GOROOT环境变量;如果你将Go放在了/usr/local/go中,则可以不设置该环境变量(Windows下是C:\go)。当然,建议不管什么时候都设置GOROOT。另外,确保$GOROOT/bin在PATH目录中。
|– VERSION — 文件,当前Go版本
|– api — 目录,包含所有API列表,方便IDE使用
|– doc — 目录,Go语言的各种文档,官网上有的,这里基本会有,这也就是为什么说可以本地搭建”官网”。这里面有不少其他资源,比如gopher图标之类的。
|– favicon.ico — 文件,官网logo
|– include — 目录,Go 基本工具依赖的库的头文件
|– lib — 目录,文档模板
|– misc — 目录,其他的一些工具,相当于大杂烩,大部分是各种编辑器的Go语言支持,还有cgo的例子等
|– robots.txt — 文件,搜索引擎robots文件
|– src — 目录,Go语言源码:基本工具(编译器等)、标准库
`– test — 目录,包含很多测试程序(并非_test.go方式的单元测试,而是包含main包的测试),包括一些fixbug测试。可以通过这个学到一些特性的使用。

学习Go语言的内部实现,主要依靠对源代码的分析,所以阅读源代码是很好的方式。linus谈到如何学习Linux内核时也说过”Read the F**ing Source code”。

使用调试器

通过gdb下断点,跟踪程序的行为。调试跟代码的方式是源代码阅读的一种辅助手段。

用户代码入口是在main.main,runtime库中的函数可以通过runtime.XXX断点捕获。比如写一个test.go:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello world!")
}

编译,调试

go build test.go
gdb test

可以在main.main处下断点,单步执行,你会发现进入了一个runtime·convT2E的函数。这个就是由于fmt.Println接受的是一个interface,而传入的是一个string,这里会做一个转换。以这个为一个突破点去跟代码,就可以研究Go语言中具体类型如何转为interface抽象类型。

分析生成的汇编代码

有时候分析会需要研究生成的汇编代码,这里介绍生成汇编代码的方法。

go tool 6g -S hello.go

-S参数表示打印出汇编代码,更多参数可以通过-h参数查看。

go tool 6g -h

或者可以反汇编生成的可执行文件:

go build test.go
go tool 6l -a test | less

本机是amd64的机器,如果是i386的机器,则命令是8g

需要注意的是用6g的-S生成的汇编代码和6l -a生成的反汇编代码是不太一样的。前者是直接对源代码进行汇编,后者是对可执行文件进行反汇编。在6l进行链接过程中,可能会在原汇编文件基础上插入新的指令。所以6l反汇编出来的是最接近真实代码的。

不过Go的汇编语法跟常用的有点不太一致,可能读起来会不太习惯。还有另一种方式,就是在用gdb调试的过程中查看汇编。

gdb test
b main.main
disas