首页 >> 大全

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

2024-01-06 大全 20 作者:考证青年

​前言:

让我们一起来了解下go build命令都做了些啥;并进行源码追踪其过程;在早期版中编译器,连接器都是用C开发的。后期版本中go的编译器连接器都用go重写了一套,这一套都是开源的,我们都可以阅读;

版本: go1.13.4 /amd64

调试工具: dlv

dlv如果不太会用的可以看一下我前一篇文章:好未来技术交流社区(TTC)

go build命令参数可选项:

可选项

备注

-n

打印编译过程

-a

将命令源码文件与库源码文件全部重新构建

-x

打印编译期间用到的命名,它与 -n 的区别是,它不仅打印还会执行

-o

输出执行文件保存的文件名

-race

开启竞态条件的检测,支持的平台有限制

一.过程解析:

gin源码分析_gcc源码解析_

输入如下命令,可以看到图中:

#go build -n default.go

gcc源码解析_gin源码分析_

上图中过程,和上图中略有不同的地方是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 
packagefile  command-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

如图所示:

gin源码分析__gcc源码解析

_gcc源码解析_gin源码分析

我打印了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中进行初始化,对应方法。

_gcc源码解析_gin源码分析

此时可以对下一个断点继续跟踪;

_gin源码分析_gcc源码解析

打印387行中的a

gin源码分析_gcc源码解析_

结构体在src/cmd/go//work/.go中,结构如下:

type Action struct {Mode       string                        // 动作操作说明Package    *load.Package                 // 此操作的工作包Deps       []*Action                     // 在此之前必须采取的行动Func       func(*Builder, *Action) error // 动作方法(nil = no-op)IgnoreFail bool                          // 即使依赖项失败,是否运行fTestOutput *bytes.Buffer                 // 测试输出缓冲区Args       []string                      // 运行程序的其他参数triggers []*Action // inverse of deps    buggyInstall bool // is this a buggy install (see -linkshared)?    TryCache func(*Builder, *Action) bool // callback for cache bypass// Generated files, directories.    Objdir   string         // 中间对象目录    Target   string         // 操作的目标:创建的包或可执行文件    built    string         // 实际创建的包或可执行文件    actionID cache.ActionID // 动作输入的缓存ID    buildID  string         // 操作输出的生成IDVetxOnly  bool       // Mode=="vet": only being called to supply info about dependencies    needVet   bool       // Mode=="build": need to fill in vet config    needBuild bool       // Mode=="build": need to do actual build (can be false if needVet is true)    vetCfg    *vetConfig // vet config    output    []byte     // output redirect buffer (nil means use b.Print)// Execution state.    pending  int         // number of deps yet to complete    priority int         // relative execution priority    Failed   bool        // whether the action failed        json     *actionJSON // action graph information
}

该结构体主要存储动作行为;比如说执行编译动作,然后通过结构体映射到对应方法;

然后我们看一下Do方法,Do方法其实就是对动作的操作;

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.go             base.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

_gin源码分析_gcc源码解析

跟踪到397行进入方法

_gcc源码解析_gin源码分析

按n继续执行

gcc源码解析_gin源码分析_

gin源码分析__gcc源码解析

这一块的是根据base.Tool获取,不同的操作系统获取的编译器不一致.继续按n执行

_gcc源码解析_gin源码分析

exec.方法其实就是调用去编译.go文件;

命令会生成一个/var//24//T/go-/b001/exe/a.out

然而/var//24//T/go-/就是我们最初看到的$work,这个每次是不一样的。

然而想得到 ,link,命令操作都可以对 cmd/go//base.Tool下断点去跟踪,看到对应的过程;

其实在调用命令之后,又调用了asm命令。

gin源码分析_gcc源码解析_

asm命令其实是生成.o文件;

除此之外,并不是每次go build时都会去调用命令,有时候则不会。在程序id一致的时候就会去取对应编译的cache文件;

如图

_gin源码分析_gcc源码解析

结束:

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

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了