本文介绍了使用轻量化 libc 库 musl 进行交叉编译生成多架构二进制的方案,最终实现在 x86_64 编译机器上能同时构建出静态链接的 x86_64 和 aarch64 二进制的目标,大大简化了多套构建脚本的开发和维护成本。
musl 是 libc 标准库的轻量化实现,遵循 POSIX 标准,它通常被认为是静态链接的更好选择:
- 体积更小,可执行文件更加精简: musl 的设计目标就是轻量化、精简实现,因此在静态链接后,二进制的体积普遍比 glibc 小很多。官方 Alpine 镜像(基于 musl)只有几 MB,而 Debian/Ubuntu 等(基于 glibc)的基础镜像往往达到几十到上百 MB。
- 依赖更少,部署更稳定:musl 的 API 与实现都相对简洁,不依赖繁多的动态加载机制,所以在静态链接场景下几乎不需要再额外维护任何兼容层或配置。
- 简单、可控的实现,利于安全审计:glibc 的代码体量庞大、历史包袱和兼容性逻辑也更多,相较之下 musl 的实现较为精简,更易于在特定需求下进行审计和维护。
- 容器与嵌入式场景需求:静态链接+小体积库特别适合容器微服务、IoT 以及嵌入式系统,这些场景通常要求资源使用最小化、系统可移植性和简洁度更高。
交叉编译是指编译器能生成和它执行环境(Host 架构)不同 CPU 架构(Target 架构)的二进制,例如在 aarch64 机器上编译出在 x86_64 机器上运行的二进制。
- Host 架构: 运行编译工具链的 CPU 架构;
- Target 架构:运行生成的二进制的 CPU 架构;
本文介绍 4 种使用 musl 库来实现 CGO 程序交叉编译+静态链接的方案。
使用 musl.cc 项目提供的预编译工具链 #
https://musl.cc/ 页面提供了 x86(i386) Host 架构的 musl 静态链接二进制工具链,它们可以在 x86 和 x86_64 架构的机器上运行,交叉编译生成其它架构的可执行程序。
https://more.musl.cc/ 页面提供了其它 Host 架构工具链,例如: https://more.musl.cc/11.2.1/x86_64-linux-musl/aarch64-linux-musl-cross.tgz 工具链的 Host 架构是 x86_64,可以编译出 Target 架构是 aarch64 的二进制。
以生成 x86_64 架构的 musl 静态链接二进制工具链(x86_64-linux-musl-XX)为例,安装步骤如下:
# 创建工具链目录
sudo mkdir -p /usr/local/musl
# 下载工具链(选择合适的 target 架构版本,如 x86_64)
wget https://musl.cc/x86_64-linux-musl-cross.tgz
# 解压到工具链目录
sudo tar -xf x86_64-linux-musl-cross.tgz -C /usr/local/musl
# 创建符号链接
sudo ln -s /usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc /usr/local/bin/x86_64-linux-musl-gcc
sudo ln -s /usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-g++ /usr/local/bin/x86_64-linux-musl-g++
# x86_64-linux-musl-gcc 编译器可以在 x86 和 x86_64 Host 架构上运行
alizj@ubuntu:~$ file /usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc
/usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
# 验证安装
x86_64-linux-musl-gcc --version
类似的,生成 aarch64 架构的 musl 静态链接二进制工具链(aarch64-linux-musl-XX),安装步骤如下:
# 创建工具链
sudo mkdir -p /usr/local/musl
# 下载工具链(选择合适的 target 架构版本,如 aarch64)
wget https://musl.cc/aarch64-linux-musl-cross.tgz
# 解压到工具链目录
sudo tar -xf aarch64-linux-musl-cross.tgz -C /usr/local/musl
# 创建符号链接
sudo ln -s /usr/local/musl/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc /usr/local/bin/aarch64-linux-musl-gcc
sudo ln -s /usr/local/musl/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ /usr/local/bin/aarch64-linux-musl-g++
# 验证安装
aarch64-linux-musl-gcc --version
安装了 x86_64-linux-musl-XX/aarch64-linux-musl-XX 交叉编译工具链后,就可以用来静态编译链接 Go CGO 程序了。以 x86_64 Target 机构为例,go build 时使用 CC/CXX 环境变量来指定使用对应的 musl 工具链:
$ export CC=x86_64-linux-musl-gcc
$ export CXX=x86_64-linux-musl-g++
$ export CGO_ENABLED=1;
$ export GOOS=linux;
$ export GOARCH=amd64;
# 静态编译链接
$ go build -ldflags="-linkmode external -extldflags '-static'" -o myagent ./cmd/main.go
# 生成静态链接程序
$ ldd /tmp/myagent
not a dynamic executable
musl.cc 和 more.muls.cc 项目的局限是它们都只提供了 Host 架构是 arm64 的二进制交叉编译工具链,只能在 x86 或 x86_64 架构的机器上运行,而不能 aarch64 机器上运行。
对于 Host 架构是 aarch64 的编译环境,可以使用 musl-cross-make 或 musl-cross 或 crosstool-ng 项目,它们都是从源码编译出可以在编译环境的机器上运行的交叉编译工具链。
使用 musl-cross-make 项目手动编译出交叉编译工具链 #
https://github.com/richfelker/musl-cross-make 项目提供了一键从源码构建出各种 Target 架构的交叉编译工具链的能力。
以 Host 架构是 aarch64 的 ubuntu 虚机,生成 Target 架构是 x86_64 的交叉编译工具链为例,步骤如下:
root@ubuntu:/home/alizj# git clone [email protected]:richfelker/musl-cross-make.git
root@ubuntu:/home/alizj# cd musl-cross-make
# 修复从 https://ftp.barfooze.de/pub/sabotage/tarballs/ 下载 linux headers 403 的问题。
root@ubuntu:/home/alizj/musl-cross-make# git diff Makefile
diff --git a/Makefile b/Makefile
index 1f4aaea..6b0cc02 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ MUSL_SITE = https://musl.libc.org/releases
MUSL_REPO = https://git.musl-libc.org/git/musl
LINUX_SITE = https://cdn.kernel.org/pub/linux/kernel
-LINUX_HEADERS_SITE = https://ftp.barfooze.de/pub/sabotage/tarballs/
+LINUX_HEADERS_SITE = https://github.com/sabotage-linux/kernel-headers/releases/download/v4.19.88-2
DL_CMD = wget -c -O
SHA1_CMD = sha1sum -c
# make install 使用 TARGET 变量指定 Target 架构类型,编译生成对应的交叉编译工具链
root@ubuntu:/home/alizj/musl-cross-make# make TARGET=x86_64-linux-musl install
# 生成的交叉编译工具链安装到 output 目录下,可以被 move 到自定义位置
root@ubuntu:/home/alizj/musl-cross-make# ls output/
bin include lib libexec share x86_64-linux-musl
root@ubuntu:/home/alizj/musl-cross-make# ls output/bin/
x86_64-linux-musl-addr2line x86_64-linux-musl-gcc-9.4.0 x86_64-linux-musl-nm
x86_64-linux-musl-ar x86_64-linux-musl-gcc-ar x86_64-linux-musl-objcopy
x86_64-linux-musl-as x86_64-linux-musl-gcc-nm x86_64-linux-musl-objdump
x86_64-linux-musl-c++ x86_64-linux-musl-gcc-ranlib x86_64-linux-musl-ranlib
x86_64-linux-musl-cc x86_64-linux-musl-gcov x86_64-linux-musl-readelf
x86_64-linux-musl-c++filt x86_64-linux-musl-gcov-dump x86_64-linux-musl-size
x86_64-linux-musl-cpp x86_64-linux-musl-gcov-tool x86_64-linux-musl-strings
x86_64-linux-musl-elfedit x86_64-linux-musl-gprof x86_64-linux-musl-strip
x86_64-linux-musl-g++ x86_64-linux-musl-ld
x86_64-linux-musl-gcc x86_64-linux-musl-ld.bfd
后续可以使用 CC=x86_64-linux-musl-gcc
和 CXX=x86_64-linux-musl-g++
环境变量来指定使用这个工具链。
使用 musl-cross 项目 #
https://github.com/musl-cross/ 项目专注于提供 musl 类型的交叉编译工具链。
它是基于另一个开源项目 https://crosstool-ng.github.io/ 实现,提供了经过验证的、预定义的 gcc/musl libc/kernel 版本组合:
- 项目的 targets 目录下,提供了各种版本组合的工具链版本配置,例如
/targets/x86_64-unknown-linux-musl/config
:
CT_LIBC_MUSL=y # 使用 musl 库,而非默认的 glibc
CT_LINUX_V_4_4=y # 指定使用 4.4 内核
其中引用的 4.4 内核是由 crosstool-ng 提供的:https://github.com/crosstool-ng/crosstool-ng/tree/master/packages/linux/4.4.302
相比 musl-cross-make 项目的优势是项目维护的比较积极,kernel、gcc、musl 等版本较新。
musl-cross github 主页提供了支持的 target 列表,如:aarch64-unknown-linux-musl,x86_64-unknown-linux-musl 等。
一键编译生成指定 target 架构的交叉编译工具链:
git clone [email protected]:musl-cross/musl-cross.git
cd musl-cross
./scripts/make x86_64-unknown-linux-musl # 指定 taget 名称,如 x86_64-unknown-linux-musl
使用 crosstool-ng 项目 #
https://crosstool-ng.github.io/ 项目是更通用、灵活的生成交叉编译工具链的项目。它支持生成基于 musl 或 glibc 的静态、动态链接工具链。
- samples 目录下提供了各种示例的交叉编译工具链配置;
- packages 目录下提供了 gcc、kernel、glibc、musl 的各版本源码;
crosstool-ng 提供了类似于 linux kernel 的 ncurse 配置界面(ct-ng menuconfig),可以灵活配置工具链各源码的类型、版本和配置参数。
以编译生成使用 musl 的 aarch64-unknown-linux-musl 交叉编译工具链为例:
# 先安装 crosstool-ng 提供的工具命令 ct-ng
VERSION=1.26.0
wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-${VERSION}.tar.bz2
tar -xvf crosstool-ng-${VERSION}.tar.bz2
cd crosstool-ng-${VERSION}
./configure --prefix=/usr/local # 安装到 /user/local 目录下
make
make install
# 验证安装成功
ct-ng help
# 查看 ct-ng 提供的预定义工具链列表
ct-ng list-samples
# 查看列表中某个工具链,如 aarch64-unknown-linux-musl 的详情
ct-ng aarch64-unknown-linux-musl
# 配置该工具链的参数
ct-ng menuconfig
# 编译构建该工具链
ct-ng build
# 将生成的工具链 bin 目录加到 PATH 环境变量中
export PATH="${PATH}:/your/toolchain/path/bin"
# 后续可以使用 CC、CROSS_COMPILE、CHOST 等环境变量来指定使用生成的工具链
make CC=aarch64-unknown-linux-musl-gcc
make CROSS_COMPILE=aarch64-unknown-linux-musl-
make CHOST=x86_64-pc-linux-gnu
使用 musl 交叉编译工具链 #
使用上面任意一种方式安装的交叉编译器 x86_64-linux-musl-XX/aarch64-linux-musl-XX 来再次对 CGO 程序进行交叉编译+静态链接。
以 ubuntu aarch64 编译环境为例,go build 时使用 CC/CXX 环境变量来指定使用能生成 x86_64 二进制的交叉编译工具链:
$ export CC=x86_64-linux-musl-gcc
$ export CXX=x86_64-linux-musl-g++
$ export CGO_ENABLED=1;
$ export GOOS=linux;
$ export GOARCH=amd64;
# 交叉编译和静态链接成功
$ go build -ldflags="-linkmode external -extldflags '-static'" -o myagent ./cmd/main.go
$ ldd /tmp/myagent
not a dynamic executable