4.5.1 类型检查逻辑 - 包加载器

go 编译器一次编译的最大单元是一个包,输出的结果叫着对象文件(Object File),go 定义了自己的对象文件格式,并以.a或者.o为后缀存在于文件系统中。在$GOROOT/pkg/$OS_$ARCH/目录下就包含了所有标准库的对象文件,例如 linux 系统该目录为:$GOROOT/pkg/linux_amd64.

加载器在编译阶段解决包依赖问题,其任务就是通过包名找到对应的对象文件,然后将其解析成为上文提到的Package对象。加载器可以有各种实现,其接口定义在文件$GCROOT/compile/internal/types2/api.go中:

type Importer interface {
	Import(path string) (*Package, error)
}

type ImportMode int

type ImporterFrom interface {
	Importer
	ImportFrom(path, dir string, mode ImportMode) (*Package, error)
}

两个接口的区别是方法ImporterFrom通过指定目标目录的方式来支持vendor 机制, 而Importer无法支持,保留Importer接口是为了兼容性考虑,ImportMode 当前没有用途。加载器的实现在$GCROOT/compile/internal/noder/import.go中,抛开具体细节,其代码框架为:

type gcimports struct {
	packages map[string]*types2.Package
}

func (m *gcimports) Import(path string) (*types2.Package, error) {
	return m.ImportFrom(path, "" /* no vendoring */, 0)
}

func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { /* 忽略方法体 */
}

可见Import只是简单地调用ImportFrom方法,而后者负责整个加载解析逻辑。加载器配置在Config中,并在类型检查的入口函数check2()中进行初始化,该函数在文件$GCROOT/compile/internal/noder/irgen.go中。

go 编译过程的最后一件事情就是将编译结果导出成对象文件,所以包加载器只是该过程的一个逆操作,当前加载器的实现也是 Go 编译器团队在重写类型检查器的过程中临时的一个实现,后续可能会修改。

对于对象文件的详细格式、导出逻辑与加载逻辑我们没有必要深挖细刨,只需要掌握如下要点即可:

  1. 对象文件是二进制格式文件,是编译器的输出,链接器的输入

  2. 对象文件分为不同的块(Section)来组织数据,例如数据块,代码块,调试信息块等

  3. 对象文件内包含重定位(Relocation)信息,链接器在将多个对象链接在一起时,主要工作就是为需要重定位的数据或者指令规划内存地址

  4. go 是静态链接语言,在生成可执行文件时,链接器会将所有依赖的包链接在一起,形成一个独立的可执行文件

  5. 可以通过工具go tool objdump来查看对象文件或者可执行文件的内容

最后更新于