跳过正文

使用 musl 交叉编译和静态链接

··2322 字
Cgo Go Compile Gcc Musl
目录

本文介绍了使用轻量化 libc 库 musl 进行交叉编译生成多架构二进制的方案,最终实现在 x86_64 编译机器上能同时构建出静态链接的 x86_64 和 aarch64 二进制的目标,大大简化了多套构建脚本的开发和维护成本。

musl 是 libc 标准库的轻量化实现,遵循 POSIX 标准,它通常被认为是静态链接的更好选择:

  1. 体积更小,可执行文件更加精简: musl 的设计目标就是轻量化、精简实现,因此在静态链接后,二进制的体积普遍比 glibc 小很多。官方 Alpine 镜像(基于 musl)只有几 MB,而 Debian/Ubuntu 等(基于 glibc)的基础镜像往往达到几十到上百 MB。
  2. 依赖更少,部署更稳定:musl 的 API 与实现都相对简洁,不依赖繁多的动态加载机制,所以在静态链接场景下几乎不需要再额外维护任何兼容层或配置。
  3. 简单、可控的实现,利于安全审计:glibc 的代码体量庞大、历史包袱和兼容性逻辑也更多,相较之下 musl 的实现较为精简,更易于在特定需求下进行审计和维护。
  4. 容器与嵌入式场景需求:静态链接+小体积库特别适合容器微服务、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-gccCXX=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 的静态、动态链接工具链。

  1. samples 目录下提供了各种示例的交叉编译工具链配置;
  2. 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

相关文章

GCC 交叉编译工具链
·6210 字
Cgo Go Compile Gcc
交叉编译是指编译器能生成和它执行环境不同的 CPU 架构的二进制,例如在 arm64 机器上编译出在 x86_64 机器上运行的二进制。 本文分别以常用的 ubuntu aarch64 和 fedora 40 x86_64 编译环境为例,介绍这两个问题的解决方案。
Go CGO 程序静态编译链接
·2347 字
Cgo Go Compile Gcc
本文先介绍 Go CGO 的概念和应用场景,以项目用到的 mattn/go-sqlite3 为例,介绍 CGO 程序的静态链接实现方案,其中涉及到动态链接的问题分析、 ubunut/centos 系统的静态编译环境搭建,CGO 静态编译遇到的问题和解决方案,最终生成最小化系统环境依赖的静态链接二进制。
链接器 ld
··6277 字
Gnu Gcc Ld
使用 linux bpftrace 进行内核和应用性能分析
·9685 字
Bpftrace Kernel Ebpf Performance Tool
介绍 bpftrace 工具的使用方式、局限性和问题。