1.3 基本技巧

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

阅读源代码

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

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

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

使用调试器

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

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. fmt.Println("hello world!")
  7. }

编译,调试

  1. go build test.go
  2. gdb test

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

分析生成的汇编代码

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

  1. go tool 6g -S hello.go

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

  1. go tool 6g -h

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

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

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

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

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

  1. gdb test
  2. b main.main
  3. disas