Go语言编译器的正确打开方式(二)- 通过Debug理解Go的编译过程

# Go语言编译器的正确打开方式(一)- 从源码编译 go 中,我们做好了debug go源码的前期准备。

学会debug跟踪Go编译的源码,对于理解Go编译过程至关重要。

通过在关键位置暂停,我们可以更深入地了解编译的不同阶段,以及AST和IR的结构

观察 go build 过程

使用go build -x命令,可以详细查看Go编译的全过程,包括 编译(compile)链接(link) 两大步骤。

以gorm代码为例,跳转码utils 目录下,并执行go build -x命令。

可以看到编译utils.go文件的具体步骤,如创建目录、写入importcfg文件等。

完整输出如下

bash 复制代码
WORK=/var/folders/nv/f11l1jld7ql771rfz_b21yhc0000gr/T/go-build3026780938
mkdir -p $WORK/b001/
cat >/var/folders/nv/f11l1jld7ql771rfz_b21yhc0000gr/T/go-build3026780938/b001/importcfg << 'EOF' # internal
# import config
packagefile database/sql/driver=/Users/hangwu/Library/Caches/go-build/2e/2e6a3a4ec553e101f2e36fd8b0049a72f9ba9e349a0f5a3c22b3b8b3ad090229-d
packagefile fmt=/Users/hangwu/Library/Caches/go-build/04/04764c52faa1d3e6e698518e1dc3b3ffae4c25ac7875fe814b3fbf9ca8963b8c-d
packagefile path/filepath=/Users/hangwu/Library/Caches/go-build/8f/8fa7f7e56ec93875e3135dd3e55a91129cfee1a57957e0ca012b09d8f12822a2-d
packagefile reflect=/Users/hangwu/Library/Caches/go-build/f7/f79d69d68777453429cb7edbe0d5be687a91314798b7943367822cefba2aff48-d
packagefile runtime=/Users/hangwu/Library/Caches/go-build/a5/a5f3be7ffcd435cd4c0f4a05cbc6191cd7f9b3820831da31d77836525ca793d0-d
packagefile strconv=/Users/hangwu/Library/Caches/go-build/5f/5f04c11b590cb2a403b65d7cc2338fb5f42b711c927b98a3b423ba42a8b8d742-d
packagefile strings=/Users/hangwu/Library/Caches/go-build/db/db137e54b24d29ba4dc77debbdee4b4a69497480b580d888ea6b2cc072fccddd-d
packagefile unicode=/Users/hangwu/Library/Caches/go-build/92/92c0c5fa656d96d5befb9e4fffc51083c1a5170e54b6876a8993ce9415d24de8-d
EOF
cd /Users/hangwu/0_studio/github.com/gorm/utils
/Users/hangwu/0_studio/github.com/golang/go/pkg/tool/darwin_arm64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p gorm.io/gorm/utils -lang=go1.18 -complete -buildid UOPFRWgEHothTi9USrEK/UOPFRWgEHothTi9USrEK -goversion go1.21.11 -c=4 -shared -nolocalimports -importcfg $WORK/b001/importcfg -pack ./utils.go
/Users/hangwu/0_studio/github.com/golang/go/pkg/tool/darwin_arm64/buildid -w $WORK/b001/_pkg_.a # internal
cp $WORK/b001/_pkg_.a /Users/hangwu/Library/Caches/go-build/0b/0bd61e4e037c153dc08d70e9cdf18fcd2b5e194695984ae0f2875fe6bdbc03f5-d # internal

启动 Debug

从上面的输出,我们注意到 compile 命令正是go编译的过程,我们需要模仿 compile 的命令,然后启动debug。

第一步,构造importcfg文件

utils目录下创建importcfg文件,将上面# import cfgEOF中间的内容拷贝到文件中。

编译过程中,依赖的模块会先被编译,并以靜态库的方式,缓存在Caches目录下,当前代码的编译会查找importcfg中指定的文件并引用。

第二步,配置GOROOT(以Goland为例)

# Go语言编译器的正确打开方式(一)- 从源码编译 go 中的go源码目录配置为GOROOT

第三步,依样画葫芦,配好go compile的运行参数

这里只需要摘抄第一步中go build -x命令过程中编译参数

bash 复制代码
/Users/hangwu/0_studio/github.com/golang/go/pkg/tool/darwin_arm64/compile -o 
$WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p gorm.io/gorm/utils -lang=go1.18 -complete -buildid UOPFRWgEHothTi9USrEK/UOPFRWgEHothTi9USrEK -goversion go1.21.11 -c=4 -shared -nolocalimports -importcfg $WORK/b001/importcfg -pack ./utils.go

如果想知道这些参数控制什么,请用命令go tool compile --help查看帮助文档

点击Run运行,配置正确的话,在gorm/utils目录下生成一个编译后的_pkg_.a文件

第四步,debug一个示例

通过阅读go源码中src/cmd/compile目录下的README.md文件,了解go编译器的结构。

要注意不同go版本的这个文件内容是不一样的,注意尽量阅读当前版本的文件。

通过这个文档,我们知道了cmd/compile/internal/syntax目录下是编译的前端,也是编译的第一阶段,包括词法分析,语法分析,构造 AST(抽像语法树)

我们找到noder.goLoadPackage函数, 在最后的unified调用处打上断点。 unify之前是 ASTunifiy之后是 IR

IR 是 Internal Representation 的缩写,将代码转换为底层操作指令的中间产物 每个编译器都会定义自己的IR,后续的阶段都是基于IR进行优化并生成最终底层代码

一个AST的例子

接下来将第三步的配置以Debug方式启动,程序会停到断点这里。

观察这里的内存变量。要知道go编译器是把每个文件都扫描生成一个AST,一对一非常直观。

看到noders数组,每个noder代表一个文件的AST,当前包下只有utils.go一个文件,所以数组长度为1。

具体函数的例子

再展开,能看到代码被扫描成不同的syntax.Decl,包括了前面是一些import语句,var全局变量定义语句,以及后面是一些函数定义。

这里的Decl还是按代码顺序整理的,所以可以比较方便的看到我们想要的。

打开最后一个Decl结构,可以看到对应代码文件中的RTrimSlice函数。它包含了两个if语句(Stmt指Statment),和一个return语句。

后续

如果上面的过程顺利,那么你就具备了跟踪编译所有阶段的能力,

通过在不同的地方打断点跟踪,甚至修改代码,可以更好地理解Go编译过程。

相关推荐
newerp13 小时前
Go net/http 标准库基础
后端·程序员·go
26519405112648013 小时前
01-整体架构与高可用
go
程序员爱钓鱼15 小时前
Go if 判断详解
前端·后端·go
名字还没想好☜1 天前
Go 的 unsafe.Pointer 实战:零拷贝 []byte↔string 转换与三条铁律
开发语言·后端·golang·go·unsafe
阿里云云原生1 天前
阿里云联合 Datadog,补齐 Go 可观测性最后短板
云原生·go
可观测性用观测云1 天前
零码改造!Go 语言应用上报观测云完整最佳实践
go
斐波那契数列1 天前
参考react 实现一个golang gui
go
程序员爱钓鱼2 天前
Go 运算符详解
后端·面试·go
angryshan2 天前
goLang配置断点流程
go·bug
名字还没想好☜2 天前
Go 的 sync.Cond 实战:用条件变量做等待/通知,比忙轮询省 CPU
开发语言·数据库·后端·golang·go