在 Golang 中开启 cgo 时遇到的问题与思考
对纯 Go 代码,可以在
go build
时添加GOOS
和GOARCH
变量来指定二进制的目标系统与架构,实现交叉编译;但对开启了 CGO 的代码,如何做到交叉编译呢?
错误的示范
总共在项目中遇到了两类错误。
环境搭建
在宿主机上使用 multipass 开启 3 个不同版本的 Ubuntu 系统,如下:
1 | multipass launch --name=a release:18.04 |
依赖安装
1 | sudo apt update |
查看当前系统 GLIBC 版本
getconf GNU_LIBC_VERSION
1
glibc 2.35
ldd --version
1
2
3
4
5ldd (Ubuntu GLIBC 2.35-0ubuntu3) 2.35
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
查看各系统的 glibc 版本
1 | multipass exec a getconf GNU_LIBC_VERSION |
详细如下:
系统版本 | glibc 版本 | |
---|---|---|
a | 18.04 | glibc 2.27 |
b | 20.04 | glibc 2.31 |
c | 22.04 | glibc 2.35 |
二进制编译时依赖的库的版本与实际运行时系统中存在的库之间的差异
在 Ubuntu 22.04 LTS 中编译的二进制,无法在 Ubuntu 18.04.6 LTS 运行(架构:amd64)。
按步骤来操作,此问题必现
在 c 机器上构建 linux amd64 架构的二进制 demo,并将 demo 分发到 a, b 两台机器上。
分别在 a, b 两台机器上执行二进制 demo。
对 demo 文件的分析
在 macOS 12 中开启 CGO 编译的二进制,无法在 macOS 11 中运行(架构:amd64)。后续未复现
二进制编译时用的系统与运行时的系统不同
GLIBC版本不兼容问题
构建 linux 的二进制文件时,做静态打包,将 glibc 的依赖一起打包进二进制文件中。
1 | GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -ldflags "-extldflags '-static'" -o $(NAME) |
但是遇到以下的 Warning,大致意思是虽然用了静态编译,但是还是会动态链接到进行 linker 操作时的 glibc 版本上。如果放任这个错误不管,可能会出现一些莫名其妙的问题。
1 | /usr/bin/ld: /tmp/go-link-922118976/000010.o: in function `unixDlOpen': |
这里一种可行的方式是不使用 glibc 这种 libc,换成 musl,在 Ubuntu 上面安装:
1 | sudo apt install -y musl musl-dev musl-tools |
编译时只需要在 go build
时添加 CC=/usr/bin/musl-gcc
变量。
1 | CC=/usr/bin/musl-gcc GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -ldflags "-extldflags '-static'" -o $(NAME)-musl |
在 c 机器上面构建完成后,分发到 a、b 机器上面,都可以正常运行,结果如下:
1 | ubuntu@b:~$ ./demo-musl up |
如何交叉编译
在 macOS (x86) 上搭建编译 linux amd64 和 arm64 平台的环境
- 安装 musl-cross
1 | brew install FiloSottile/musl-cross/musl-cross --with-aarch64 |
安装完成后,可以看到对应架构的编译器
- 编译
1 | BUILD_FLAGS="-X $(PKG)/version.Commit=$(COMMIT)\ |
- amd64 架构
1 | GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC="/usr/local/bin/x86_64-linux-musl-gcc" go build -ldflags $(BUILD_FLAGS) -o $(NAME)-amd64 |
- arm64 架构
1 | GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC="/usr/local/bin/aarch64-linux-musl-gcc" go build -ldflags $(BUILD_FLAGS) -o $(NAME)-arm64 |
在 macOS (x86) 上搭建编译 windows amd64 架构的环境
1 | brew install mingw-w64 |
- amd64 架构
1 | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC="/usr/local/bin/x86_64-w64-mingw32-gcc" go build -ldflags $(BUILD_FLAGS) -o $(NAME).exe |
Reference
- Go语言涉及CGO的交叉编译(跨平台编译)解决办法 - 知乎 (zhihu.com)
- 含有CGO代码的项目如何实现跨平台编译 - SegmentFault 思否
- Statically compiled Go programs, always, even with cgo, using musl (honnef.co)
- go - 使用 cgo、LuaJIT 和 musl 构建静态二进制文件 - IT工具网 (coder.work)
- mattn/go-sqlite3: sqlite3 driver for go using database/sql (github.com)
- 《程序员的自我修养——链接、装载与库》
在 Golang 中开启 cgo 时遇到的问题与思考