在 Golang 中开启 cgo 时遇到的问题与思考

对纯 Go 代码,可以在 go build 时添加 GOOSGOARCH 变量来指定二进制的目标系统与架构,实现交叉编译;但对开启了 CGO 的代码,如何做到交叉编译呢?

错误的示范

总共在项目中遇到了两类错误。

环境搭建

在宿主机上使用 multipass 开启 3 个不同版本的 Ubuntu 系统,如下:

1
2
3
4
5
6
7
8
9
10
multipass launch --name=a release:18.04
multipass launch --name=b release:20.04
multipass launch --name=c release:22.04

# 虚拟机列表
$ multipass list
Name State IPv4 Image
a Running 172.16.73.16 Ubuntu 18.04 LTS
b Running 172.16.73.17 Ubuntu 20.04 LTS
c Running 172.16.73.18 Ubuntu 22.04 LTS

依赖安装

1
2
3
sudo apt update
sudo apt upgrade
sudo apt install -y make golang-go

查看当前系统 GLIBC 版本

  • getconf GNU_LIBC_VERSION

    1
    glibc 2.35
  • ldd --version

    1
    2
    3
    4
    5
    ldd (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
2
3
multipass exec a getconf GNU_LIBC_VERSION
multipass exec b getconf GNU_LIBC_VERSION
multipass exec c 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 两台机器上。

      1pnLi71pnLi7

    • 分别在 a, b 两台机器上执行二进制 demo。

    a机器上

    uhFx0CuhFx0C

    • 对 demo 文件的分析

      8Iojys8Iojys

  • 在 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
2
3
4
/usr/bin/ld: /tmp/go-link-922118976/000010.o: in function `unixDlOpen':
/home/ubuntu/go/pkg/mod/github.com/mattn/[email protected]/sqlite3-binding.c:39881: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/usr/bin/ld: /tmp/go-link-922118976/000016.o: in function `_cgo_6cc2654a8ed3_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:58: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

这里一种可行的方式是不使用 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
2
3
4
5
6
7
8
9
10
11
12
13
ubuntu@b:~$ ./demo-musl  up
INFO[2022-07-20 21:54:09.228] done init db
INFO[2022-07-20 21:54:09.228] scheduler started...
INFO[2022-07-20 21:54:09.229] Connecting to wss://xxx/ncd/ws/connection
INFO[2022-07-20 21:54:09.362] Response: &{Status:101 Switching Protocols StatusCode:101 Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Connection:[upgrade] Date:[Wed, 20 Jul 2022 13:54:05 GMT] Sec-Websocket-Accept:[7PJbg/gEi9umEJ4bnOaWAciTq0k=] Server:[Nginx] Upgrade:[websocket]] Body:{Reader:0xc00042f9e0} ContentLength:0 TransferEncoding:[] Close:false Uncompressed:false Trailer:map[] Request:0xc0000ad000 TLS:<nil>}
INFO[2022-07-20 21:54:09.363] send message: [RequestId: 015e63a3-7bad-4cba-a3d6-c515e61f6e42, Command: GET_AGENT_UPDATE_INFO, Code: 0, Message: , MaxFramePayload: 0, TimeoutSeconds: 30]

ubuntu@a:~$ ./demo-musl up
INFO[2022-07-20 21:57:37.345] done init db
INFO[2022-07-20 21:57:37.345] scheduler started...
INFO[2022-07-20 21:57:37.347] Connecting to wss://xxx/ncd/ws/connection
INFO[2022-07-20 21:57:37.560] Response: &{Status:101 Switching Protocols StatusCode:101 Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Connection:[upgrade] Date:[Wed, 20 Jul 2022 13:57:34 GMT] Sec-Websocket-Accept:[NgGXU/yXwW9Hk6ecEhVICTjmKiI=] Server:[Nginx] Upgrade:[websocket]] Body:{Reader:0xc00061b1d0} ContentLength:0 TransferEncoding:[] Close:false Uncompressed:false Trailer:map[] Request:0xc0000a5100 TLS:<nil>}
INFO[2022-07-20 21:57:37.561] send message: [RequestId: eb05129e-3722-4ecd-88f5-23cb2e734df3, Command: GET_AGENT_UPDATE_INFO, Code: 0, Message: , MaxFramePayload: 0, TimeoutSeconds: 30]

如何交叉编译

在 macOS (x86) 上搭建编译 linux amd64 和 arm64 平台的环境

  1. 安装 musl-cross
1
brew install FiloSottile/musl-cross/musl-cross --with-aarch64

安装完成后,可以看到对应架构的编译器

iWZkLniWZkLn

  1. 编译
1
2
3
BUILD_FLAGS="-X $(PKG)/version.Commit=$(COMMIT)\
-X '$(PKG)/version.BuildTime=$(BUILD_TIME)'\
-X '$(PKG)/version.Version=$(VERSION)' -extldflags '-static'"
  • 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

在 Golang 中开启 cgo 时遇到的问题与思考

https://eucham.me/2022/07/20/adb9a934b8da.html

作者

遇寻

发布于

2022-07-20

更新于

2022-07-30

许可协议

评论