一 【Golang源码分析】深度解析执行命令go build

?前言:
让我们一起来了解下go build命令都做了些啥;并进行源码追踪其过程;在早期版中编译器 , 连接器都是用C开发的 。后期版本中go的编译器连接器都用go重写了一套,这一套都是开源的,我们都可以阅读;
版本: go1.13.4 /amd64
调试工具:dlv
dlv如果不太会用的可以看一下我前一篇文章:好未来技术交流社区(TTC)
go build命令参数可选项:
可选项
备注
-n
打印编译过程
-a
将命令源码文件与库源码文件全部重新构建
-x
打印编译期间用到的命名,它与 -n 的区别是,它不仅打印还会执行
-o
输出执行文件保存的文件名
-race
开启竞态条件的检测,支持的平台有限制
一.过程解析:

一  【Golang源码分析】深度解析执行命令go build

文章插图
输入如下命令,可以看到图中:
#go build -n default.go
一  【Golang源码分析】深度解析执行命令go build

文章插图
上图中过程,和上图中略有不同的地方是cat >$WORK/b001/.go 这行没有写入;这一块是由于gomod的缘故;这个不是关键点所以没有写入;
#创建目录 mkdir -p $WORK/b001/#编译文件 cd /data/webroot/qingke/godemo/dump /usr/local/Cellar/go/1.13.4/libexec/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid Rw-tIgJPD3wmhn5zj7iu/Rw-tIgJPD3wmhn5zj7iu -goversion go1.13.4 -D _/data/webroot/qingke/godemo/dump -importcfg $WORK/b001/importcfg -pack -c=4 ./default.go $WORK/b001/_gomod_.go #生成链接库配置importcfg.link文件 cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefilecommand-line-arguments=$WORK/b001/_pkg_.a packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a packagefile internal/bytealg=/usr/local/go/pkg/darwin_amd64/internal/bytealg.a packagefile internal/cpu=/usr/local/go/pkg/darwin_amd64/internal/cpu.a packagefile runtime/internal/atomic=/usr/local/go/pkg/darwin_amd64/runtime/internal/atomic.a packagefile runtime/internal/math=/usr/local/go/pkg/darwin_amd64/runtime/internal/math.a packagefile runtime/internal/sys=/usr/local/go/pkg/darwin_amd64/runtime/internal/sys.a EOF #创建b001中exe目录 mkdir -p $WORK/b001/exe/ cd . #连接生成a.out可执行文件 /usr/local/Cellar/go/1.13.4/libexec/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=TwK52M06SyTj6MiQ9MRi/Rw-tIgJPD3wmhn5zj7iu/Rw-tIgJPD3wmhn5zj7iu/TwK52M06SyTj6MiQ9MRi -extld=clang $WORK/b001/_pkg_.a #更新a.out id/usr/local/Cellar/go/1.13.4/libexec/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal #mv a.out改变名为default的可执行程序 mv $WORK/b001/exe/a.out default
上面部分有三个命令link、、这三个命令是编译的核心;
: 编译器
link:连接器
: ID生成器
二.源码分析:
执行调试go命令,dlv需要用完整路径,可以用which go查看一下
命令执行如下:
dlv exec /usr/local/Cellar/go/1.13.4/libexec/bin/go build default.go
设置字符串打印长度,为了查看变量,不设置的话,变量看不完整
(dlv)config max-string-len 99999
然后下一个断点:
(dlv)b main.main
如图所示:
一  【Golang源码分析】深度解析执行命令go build

文章插图

一  【Golang源码分析】深度解析执行命令go build

文章插图
我打印了args数组的结果,其实就是我们go后面的build 和 .go参数;
在此时我们可要注意src/cmd/go/main.go代码中有base.Go.的初始化 , 可以简单看一下结构
结构原型在src/cmd/go//base/base.go代码中:
type Command struct {//命令运行方法Run func(cmd *Command, args []string)//命令行提示信息UsageLine string//go help中简短描述Short string//go help中详细描述Long string//命令标志Flag flag.FlagSet//CustomFlags指示命令将执行其自己的标志解析 。CustomFlags bool// Commands lists the available commands and help topics.// The order here is the order in which they are printed by 'go help'.// Note that subcommands are in general best avoided.Commands []*Command }
此时可以可以注意到base.Go.中的work.对应的就是我们build命令的映射 。
在我们的src/cmd/go//work/build.go中;work.的初始化在build.go中进行初始化,对应方法 。
一  【Golang源码分析】深度解析执行命令go build

文章插图
此时可以对下一个断点继续跟踪;
一  【Golang源码分析】深度解析执行命令go build

文章插图
打印387行中的a
一  【Golang源码分析】深度解析执行命令go build

文章插图
结构体在src/cmd/go//work/.go中,结构如下:
type Action struct {Modestring// 动作操作说明Package*load.Package// 此操作的工作包Deps[]*Action// 在此之前必须采取的行动Funcfunc(*Builder, *Action) error // 动作方法(nil = no-op)IgnoreFail bool// 即使依赖项失败,是否运行fTestOutput *bytes.Buffer// 测试输出缓冲区Args[]string// 运行程序的其他参数triggers []*Action // inverse of depsbuggyInstall bool // is this a buggy install (see -linkshared)?TryCache func(*Builder, *Action) bool // callback for cache bypass// Generated files, directories.Objdirstring// 中间对象目录Targetstring// 操作的目标:创建的包或可执行文件builtstring// 实际创建的包或可执行文件actionID cache.ActionID // 动作输入的缓存IDbuildIDstring// 操作输出的生成IDVetxOnlybool// Mode=="vet": only being called to supply info about dependenciesneedVetbool// Mode=="build": need to fill in vet configneedBuild bool// Mode=="build": need to do actual build (can be false if needVet is true)vetCfg*vetConfig // vet configoutput[]byte// output redirect buffer (nil means use b.Print)// Execution state.pendingint// number of deps yet to completepriority int// relative execution priorityFailedbool// whether the action failedjson*actionJSON // action graph information}
该结构体主要存储动作行为;比如说执行编译动作,然后通过结构体映射到对应方法;
然后我们看一下Do方法,Do方法其实就是对动作的操作;
【一【Golang源码分析】深度解析执行命令go build】func (b *Builder) Do(root *Action) {...// Write action graph, without timing information, in case we fail and exit early.writeActionGraph := func() {if file := cfg.DebugActiongraph; file != "" {if strings.HasSuffix(file, ".go") {// Do not overwrite Go source code in:// go build -debug-actiongraph x.gobase.Fatalf("go: refusing to write action graph to %v\n", file)}js := actionGraphJSON(root)if err := ioutil.WriteFile(file, []byte(js), 0666); err != nil {fmt.Fprintf(os.Stderr, "go: writing action graph: %v\n", err)base.SetExitStatus(1)}}}writeActionGraph()b.readySema = make(chan bool, len(all))...//Handle运行单个操作并负责触发因此可运行的任何操作 。handle := func(a *Action) {if a.json != nil {a.json.TimeStart = time.Now()}var err errorif a.Func != nil && (!a.Failed || a.IgnoreFail) {err = a.Func(b, a)//执行事件动作}...}...// Write action graph again, this time with timing information.writeActionGraph()}
请注意之前打印387行中的a的
动作其实会执行build方法,其实我们可以针对build方法下一个断点;执行
(dlv) b cmd/go/internal/work.(*Builder).build (dlv) c (dlv) bt
一  【Golang源码分析】深度解析执行命令go build

文章插图
跟踪到397行进入方法
一  【Golang源码分析】深度解析执行命令go build

文章插图
按n继续执行
一  【Golang源码分析】深度解析执行命令go build

文章插图

一  【Golang源码分析】深度解析执行命令go build

文章插图
这一块的是根据base.Tool获取,不同的操作系统获取的编译器不一致.继续按n执行
一  【Golang源码分析】深度解析执行命令go build

文章插图
exec.方法其实就是调用去编译.go文件;
命令会生成一个/var//24//T/go-/b001/exe/a.out
然而/var//24//T/go-/就是我们最初看到的$work,这个每次是不一样的 。
然而想得到,link,命令操作都可以对 cmd/go//base.Tool下断点去跟踪,看到对应的过程;
其实在调用命令之后,又调用了asm命令 。
一  【Golang源码分析】深度解析执行命令go build

文章插图
asm命令其实是生成.o文件;
除此之外,并不是每次go build时都会去调用命令 , 有时候则不会 。在程序id一致的时候就会去取对应编译的cache文件;
如图
一  【Golang源码分析】深度解析执行命令go build

文章插图
结束:
分析源码是一个比较枯燥的过程 , 需要反复尝试 。不过这个过程能让你学习到一些设计理解 , 对于成长是有帮助的,大家一起共勉;