系统总结了使用 gcc、clang、rustc 编译器进行程序的编译和链接过程,以及使用 musl 进行静态链接的方案。
交叉编译 #
交叉编译指:在一个平台(Host 架构)上生成另一种平台(Target 架构)可运行的二进制。
- Host 架构:当前运行编译工具链的架构;
- Target 架构:生成的二进制将运行的目标架构;
常见例子:在 aarch64 机器上构建 x86_64 可执行文件。
gcc Target Triplet #
标准格式:<cpu>-<vendor>-<os>
,其中:
- cpu:架构标识,如 x86_64、aarch64、arm;使用
uname -m
命令来获取。 - vendor:硬件厂商,如 pc、unknown(常见于 linux 系统)、apple;
- os:操作系统及 ABI,示例包括:
- linux-gnu:glibc
- linux-musl:musl libc
- linux-gnueabi[hf]:嵌入式 GNU EABI
- none 或 elf:裸机目标,如嵌入式程序固件
triplet 示例:
# aarch64 架构
aarch64-unknown-linux-gnu # 主流 glibc
aarch64-unknown-linux-musl # 静态链接 musl libc
aarch64-none-elf # 裸机目标(MCU 固件)
# arm32 架构
arm-unknown-linux-gnueabihf # 硬浮点 glibc
arm-unknown-linux-musleabi # 软浮点 musl
# x86_64 架构
x86_64-unknown-linux-gnu # 主流 glibc
x86_64-unknown-linux-musl # 静态 musl
gcc 只支持编译生成一种特定架构的二进制,使用 gcc -dumpmachine
查看支持的 triplet。
alizj@ubuntu:~$ gcc -dumpmachine
aarch64-linux-gnu
对于 autoconf、make、go build 等命令,可以通过 CC
环境变量来指定使用的工具链,或 --host=aarch64-linux-gnu
来指定架构前缀。
gcc 原生工具链 #
完整的 gcc 工具链,除了包含 gcc 编译器外,还包含 cpp、binutils(提供了对应架构的链接器 ld)、libc 库等包:
相关包:
alizj@ubuntu:~$ apt list --installed |grep gcc |grep arm64 |grep -v x86-64
gcc-13-aarch64-linux-gnu/noble-updates,noble-security,now 13.3.0-6ubuntu2~24.04 arm64 [installed,automatic]
gcc-13-base/noble-updates,noble-security,now 13.3.0-6ubuntu2~24.04 arm64 [installed,automatic]
gcc-13/noble-updates,noble-security,now 13.3.0-6ubuntu2~24.04 arm64 [installed,automatic]
gcc-14-base/noble-updates,noble-security,now 14.2.0-4ubuntu2~24.04 arm64 [installed]
gcc-aarch64-linux-gnu/noble,now 4:13.2.0-7ubuntu1 arm64 [installed,automatic]
gcc-doc/noble,now 4:13.2.0-7ubuntu1 arm64 [installed]
gcc/noble,now 4:13.2.0-7ubuntu1 arm64 [installed,automatic]
libgcc-13-dev/noble-updates,noble-security,now 13.3.0-6ubuntu2~24.04 arm64 [installed,automatic]
libgcc-s1/noble-updates,noble-security,now 14.2.0-4ubuntu2~24.04 arm64 [installed]
提供:
/usr/bin/gcc 和 /usr/bin/aarch64-linux-gnu-*
等二进制;/usr/lib/gcc/aarch64-linux-gnu/13
和/usr/libexec/gcc/aarch64-linux-gnu/13
: 包含 GCC 自身的头文件、库文件和调用的二进制。
二进制间链接关系:gcc -> gcc-13 -> aarch64-linux-gnu-gcc-13
alizj@ubuntu:~$ ls -l /usr/bin/gcc
lrwxrwxrwx 1 root root 6 Jan 31 2024 /usr/bin/gcc -> gcc-13
alizj@ubuntu:~$ ls -l /usr/bin/gcc-13
lrwxrwxrwx 1 root root 24 Sep 4 22:44 /usr/bin/gcc-13 -> aarch64-linux-gnu-gcc-13
alizj@ubuntu:~$ ls -l /usr/bin/aarch64-linux-gnu-gcc-13
-rwxr-xr-x 1 root root 990040 Sep 4 22:44 /usr/bin/aarch64-linux-gnu-gcc-13
alizj@ubuntu:~$ dpkg -L gcc
...
/usr/bin/c89-gcc
/usr/bin/c99-gcc
/usr/lib/bfd-plugins
...
/usr/bin/gcc
/usr/bin/gcc-ar # 打包为静态库
/usr/bin/gcc-nm # 解析 ELF 符号
/usr/bin/gcc-ranlib # 为静态库生成索引
/usr/bin/gcov
/usr/bin/gcov-dump
/usr/bin/gcov-tool
/usr/bin/lto-dump
/usr/lib/bfd-plugins/liblto_plugin.so
alizj@ubuntu:~$ dpkg -L gcc-13
...
/usr/bin/gcc-13
/usr/bin/gcc-ar-13
/usr/bin/gcc-nm-13
/usr/bin/gcc-ranlib-13
/usr/bin/gcov-13
/usr/bin/gcov-dump-13
/usr/bin/gcov-tool-13
/usr/bin/lto-dump-13
...
alizj@ubuntu:~$ dpkg -L gcc-aarch64-linux-gnu
...
/usr/bin/aarch64-linux-gnu-gcc
/usr/bin/aarch64-linux-gnu-gcc-ar
/usr/bin/aarch64-linux-gnu-gcc-nm
/usr/bin/aarch64-linux-gnu-gcc-ranlib
/usr/bin/aarch64-linux-gnu-gcov
/usr/bin/aarch64-linux-gnu-gcov-dump
/usr/bin/aarch64-linux-gnu-gcov-tool
/usr/bin/aarch64-linux-gnu-lto-dump
...
alizj@ubuntu:~$ dpkg -L gcc-13-aarch64-linux-gnu
...
/usr/bin/aarch64-linux-gnu-gcc-13
/usr/bin/aarch64-linux-gnu-gcc-ar-13
/usr/bin/aarch64-linux-gnu-gcc-nm-13
/usr/bin/aarch64-linux-gnu-gcc-ranlib-13
/usr/bin/aarch64-linux-gnu-gcov-13
/usr/bin/aarch64-linux-gnu-gcov-dump-13
/usr/bin/aarch64-linux-gnu-gcov-tool-13
/usr/bin/aarch64-linux-gnu-lto-dump-13
...
/usr/lib/gcc/aarch64-linux-gnu/13
/usr/lib/gcc/aarch64-linux-gnu/13/libgomp.spec
/usr/lib/gcc/aarch64-linux-gnu/13/libhwasan_preinit.o
/usr/lib/gcc/aarch64-linux-gnu/13/libitm.spec
/usr/lib/gcc/aarch64-linux-gnu/13/libsanitizer.spec
/usr/lib/gcc/aarch64-linux-gnu/13/plugin
/usr/lib/gcc/aarch64-linux-gnu/13/plugin/libcc1plugin.so.0.0.0
/usr/lib/gcc/aarch64-linux-gnu/13/plugin/libcp1plugin.so.0.0.0
...
# /usr/libexec/gcc 保存 gcc 自身使用的内部命令和库
/usr/libexec/gcc/aarch64-linux-gnu
/usr/libexec/gcc/aarch64-linux-gnu/13
/usr/libexec/gcc/aarch64-linux-gnu/13/collect2
/usr/libexec/gcc/aarch64-linux-gnu/13/liblto_plugin.so
/usr/libexec/gcc/aarch64-linux-gnu/13/lto-wrapper
/usr/libexec/gcc/aarch64-linux-gnu/13/lto1
...
/usr/lib/gcc/aarch64-linux-gnu/13/libcc1.so
/usr/lib/gcc/aarch64-linux-gnu/13/liblto_plugin.so
/usr/lib/gcc/aarch64-linux-gnu/13/plugin/libcc1plugin.so
/usr/lib/gcc/aarch64-linux-gnu/13/plugin/libcc1plugin.so.0
/usr/lib/gcc/aarch64-linux-gnu/13/plugin/libcp1plugin.so
/usr/lib/gcc/aarch64-linux-gnu/13/plugin/libcp1plugin.so.0
/usr/share/doc/gcc-13-aarch64-linux-gnu
# libgcc-13-dev 包提供了 gcc 提供的内置函数实现的动态库、静态库以及头文件,它们会被链接到 C 程序中,提供了 main 函数前后逻辑:
alizj@ubuntu:~$ dpkg -L libgcc-13-dev
...
# C 启动例程,调用 main 函数
/usr/lib/gcc/aarch64-linux-gnu/13/crtbegin.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtbeginS.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtbeginT.o
# C 结束例程,main 返回后执行
/usr/lib/gcc/aarch64-linux-gnu/13/crtend.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtendS.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtfastmath.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtoffloadbegin.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtoffloadend.o
/usr/lib/gcc/aarch64-linux-gnu/13/crtoffloadtable.o
# GCC glibc 标准库的实现
/usr/lib/gcc/aarch64-linux-gnu/13/include
/usr/lib/gcc/aarch64-linux-gnu/13/include/backtrace.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/float.h
...
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdalign.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdarg.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdatomic.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdbool.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stddef.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdfix.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdint-gcc.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdint.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/stdnoreturn.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/syslimits.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/unwind.h
/usr/lib/gcc/aarch64-linux-gnu/13/include/varargs.h
# GCC 自身使用的库
/usr/lib/gcc/aarch64-linux-gnu/13/libasan.a
/usr/lib/gcc/aarch64-linux-gnu/13/libasan_preinit.o
/usr/lib/gcc/aarch64-linux-gnu/13/libatomic.a
/usr/lib/gcc/aarch64-linux-gnu/13/libbacktrace.a
/usr/lib/gcc/aarch64-linux-gnu/13/libgcc.a
/usr/lib/gcc/aarch64-linux-gnu/13/libgcc_eh.a
/usr/lib/gcc/aarch64-linux-gnu/13/libgcc_s.so
/usr/lib/gcc/aarch64-linux-gnu/13/libgcov.a
/usr/lib/gcc/aarch64-linux-gnu/13/libgomp.a
/usr/lib/gcc/aarch64-linux-gnu/13/libhwasan.a
/usr/lib/gcc/aarch64-linux-gnu/13/libitm.a
/usr/lib/gcc/aarch64-linux-gnu/13/liblsan.a
/usr/lib/gcc/aarch64-linux-gnu/13/liblsan_preinit.o
/usr/lib/gcc/aarch64-linux-gnu/13/libssp_nonshared.a
/usr/lib/gcc/aarch64-linux-gnu/13/libtsan.a
/usr/lib/gcc/aarch64-linux-gnu/13/libtsan_preinit.o
/usr/lib/gcc/aarch64-linux-gnu/13/libubsan.a
/usr/lib/gcc/aarch64-linux-gnu/13/libasan.so
/usr/lib/gcc/aarch64-linux-gnu/13/libatomic.so
/usr/lib/gcc/aarch64-linux-gnu/13/libgomp.so
/usr/lib/gcc/aarch64-linux-gnu/13/libhwasan.so
/usr/lib/gcc/aarch64-linux-gnu/13/libitm.so
/usr/lib/gcc/aarch64-linux-gnu/13/liblsan.so
/usr/lib/gcc/aarch64-linux-gnu/13/libtsan.so
/usr/lib/gcc/aarch64-linux-gnu/13/libubsan.so
/usr/share/doc/libgcc-13-dev
gcc 内置了内部调用的二进制搜索路径、gcc 和 glibc 库搜索路径,它们都是和 target triplet 相关的,所以:
- 系统可以安装多个 triplet 的编译工具链和 glibc 库而不会相互混淆;
- 编译 Host 架构二进制:一般使用不带前缀的
gcc
命令; - 交叉编译:使用对应前缀架构的 gcc 命令,如
aarch64-linux-gnu-gcc
,它会自动查找对应架构的二进制、库和头文件;
- CGO、makefile 等使用变量 CC=x86_64-linux-gnu-gcc 来指定编译器。
alizj@ubuntu:~$ gcc -print-search-dirs
install: /usr/lib/gcc/aarch64-linux-gnu/13/
programs: =/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin/
libraries: =/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib/:/lib/aarch64-linux-gnu/13/:/lib/aarch64-linux-gnu/:/lib/../lib/:/usr/lib/aarch64-linux-gnu/13/:/usr/lib/aarch64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../:/lib/:/usr/lib/
gcc 交叉编译工具链 #
根据经验,ubuntu 对多架构交叉编译的支持比 centos、fedora 更成熟,后者需要 hack gcc 交叉编译工具链的 sysroot 参数。
在 ubuntu aarch64 架构机器上交叉编译出 x86_64 架构静态二进制方案:
# arm64 虚机上,为 CGO 程序交叉编译生成 amd64 程序报错
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o myagent ./cmd/
runtime/cgo
gcc: error: unrecognized command-line option '-m64'
# 这是由于安装的 ubuntu 安装的 gcc 只支持编译生成 arm64 架构的二进制,而不支持交叉编译生成 amd64 架构的二进制。
# 安装 amd64 架构的 libc 库和 gcc 交叉编译工具链
sudo apt update
sudo apt install libc6-dev-amd64-cross gcc-x86-64-linux-gnu
$ file /usr/bin/x86_64-linux-gnu-gcc
/usr/bin/x86_64-linux-gnu-gcc: symbolic link to x86_64-linux-gnu-gcc-13
$ file /usr/bin/x86_64-linux-gnu-gcc-13
/usr/bin/x86_64-linux-gnu-gcc-13: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=ad41a19a0955b7e6770ad00ff5cd9b97999b2f0e, for GNU/Linux 3.7.0, stripped
# 启用 CGO 的情况下,交叉编译静态链接成功
$ CC=x86_64-linux-gnu-gcc GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags 'osusergo netgo sqlite_omit_load_extension no_dynamic_plugins' -o myagent -ldflags=" -extldflags '-static'" ./cmd/
安装的包如下:
# 安装了如下 x86_64 相关的包,它们的二进制都以 `x86_64-linux-gnu-` 开头,如 x86_64-linux-gnu-gcc:
# 重要的包:binutils、cpp、gcc
root@ubuntu:~# dpkg -l |grep -i x86
ii binutils-x86-64-linux-gnu 2.42-4ubuntu2.5 arm64 GNU binary utilities, for x86-64-linux-gnu target
ii cpp-13-x86-64-linux-gnu 13.3.0-6ubuntu2~24.04cross1 arm64 GNU C preprocessor for x86_64-linux-gnu
ii cpp-x86-64-linux-gnu 4:13.2.0-7ubuntu1 arm64 GNU C preprocessor (cpp) for the amd64 architecture
ii gcc-13-x86-64-linux-gnu 13.3.0-6ubuntu2~24.04cross1 arm64 GNU C compiler for the x86_64-linux-gnu architecture
ii gcc-13-x86-64-linux-gnu-base:arm64 13.3.0-6ubuntu2~24.04cross1 arm64 GCC, the GNU Compiler Collection (base package)
ii gcc-x86-64-linux-gnu 4:13.2.0-7ubuntu1 arm64 GNU C compiler for the amd64 architecture
# 这些可执行程序也都是 aarch64 架构,所以可以直接执行
root@ubuntu:~# file /usr/bin/x86_64-linux-gnu-gcc-13
/usr/bin/x86_64-linux-gnu-gcc-13: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=ad41a19a0955b7e6770ad00ff5cd9b97999b2f0e, for GNU/Linux 3.7.0, stripped
# 还安装了交叉编译时依赖的其它 x86_64 包,它们位于 /usr/x86_64-linux-gnu 或 /usr/lib/gcc-cross/x86_64-linux-gnu 目录下
# 重要的包:libc、libgcc、libstdc++
root@ubuntu:~# dpkg -l |grep -i amd64-cross
ii libasan8-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all AddressSanitizer -- a fast memory error detector
ii libatomic1-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all support library providing __atomic built-in functions
ii libc6-amd64-cross 2.39-0ubuntu8cross1 all GNU C Library: Shared libraries (for cross-compiling)
ii libc6-dev-amd64-cross 2.39-0ubuntu8cross1 all GNU C Library: Development Libraries and Header Files (for cross-compiling)
ii libgcc-13-dev-amd64-cross 13.3.0-6ubuntu2~24.04cross1 all GCC support library (development files)
ii libgcc-s1-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all GCC support library (amd64)
ii libgomp1-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all GCC OpenMP (GOMP) support library
ii libhwasan0-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all AddressSanitizer -- a fast memory error detector
ii libitm1-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all GNU Transactional Memory Library
ii liblsan0-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all LeakSanitizer -- a memory leak detector (runtime)
ii libquadmath0-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all GCC Quad-Precision Math Library
ii libstdc++6-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all GNU Standard C++ Library v3 (amd64)
ii libtsan2-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all ThreadSanitizer -- a Valgrind-based detector of data races (runtime)
ii libubsan1-amd64-cross 14.2.0-4ubuntu2~24.04cross1 all UBSan -- undefined behaviour sanitizer (runtime)
ii linux-libc-dev-amd64-cross 6.8.0-25.25cross1 all Linux Kernel Headers for development (for cross-compiling)
# 和 x86_64-linux-gnu-* 可执行程序不同,这些安装的库和动态链接器都是 x86_64 架构的:
root@ubuntu:~# dpkg -L libc6-amd64-cross |grep libc.so
/usr/x86_64-linux-gnu/lib/libc.so.6
root@ubuntu:~# file /usr/x86_64-linux-gnu/lib/libc.so.6
/usr/x86_64-linux-gnu/lib/libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bd73d634962261107bcc3d54656dea90f6de8945, for GNU/Linux 3.2.0, stripped
root@ubuntu:~# file /lib64/ld-linux-x86-64.so.2
/lib64/ld-linux-x86-64.so.2: symbolic link to ../lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
root@ubuntu:~# file /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), static-pie linked, BuildID[sha1]=1c8db5f83bba514f8fd5f1fb6d7be975be1bb855, stripped
Ubuntu aarch64 官方提供了 aarch64、x86_64 架构的编译工具链,它们的二进制文件名都包含对应架构前缀:
- 原生 aarch64 架构,glibc 环境(ABI):
gcc-aarch64-linux-gnu、libc6:arm64
- 编译器:
gcc、aarch64-linux-gnu-gcc
- 原生 aarch64 架构,musl 环境(ABI):
musl, musl-dev, musl-tools
- 编译器:
aarch64-linux-musl-gcc、musl-gcc
, 它们其实是原生 gcc 编译器的 wrapper 脚本,通过自定义 spec 方式链接到 musl 库。
- x86_64 交叉编译工具链,glibc 环境:
gcc-14-x86-64-linux-gnu、binutils-x86-64-linux-gnu、libc6-amd64-cross
- 编译器:
x86_64-linux-gnu-gcc
各编译器根据自己支持的架构类型来查找和使用 /lib/
root@ubuntu:/Users/alizj/code/rust/my-demo# ls -ld /lib/*-linux-*
drwxr-xr-x 1 root root 33774 May 27 21:03 /lib/aarch64-linux-gnu
drwxr-xr-x 1 root root 240 Jan 15 21:06 /lib/aarch64-linux-musl
drwxr-xr-x 1 root root 1092 Apr 5 14:10 /lib/x86_64-linux-gnu
root@ubuntu:/Users/alizj/code/rust/my-demo# ls /usr/lib/gcc-cross/
x86_64-linux-gnu
# 搜索原生架构目录:/usr/libexec/gcc/aarch64-linux-gnu/13/ 和 /usr/lib/aarch64-linux-gnu/13/
root@ubuntu:~# gcc -print-search-dirs
install: /usr/lib/gcc/aarch64-linux-gnu/13/
programs: =/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/13/:/usr/libexec/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin/
libraries: =/usr/lib/gcc/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/13/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib/:/lib/aarch64-linux-gnu/13/:/lib/aarch64-linux-gnu/:/lib/../lib/:/usr/lib/aarch64-linux-gnu/13/:/usr/lib/aarch64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/lib/:/usr/lib/gcc/aarch64-linux-gnu/13/../../../:/lib/:/usr/lib/
# 搜索交叉编译架构目录:/usr/libexec/gcc-cross/x86_64-linux-gnu/13/ 和 /usr/lib/gcc-cross/x86_64-linux-gnu/13/
root@ubuntu:~# x86_64-linux-gnu-gcc -print-search-dirs
install: /usr/lib/gcc-cross/x86_64-linux-gnu/13/
programs: =/usr/libexec/gcc-cross/x86_64-linux-gnu/13/:/usr/libexec/gcc-cross/x86_64-linux-gnu/13/:/usr/libexec/gcc-cross/x86_64-linux-gnu/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/:/usr/lib/gcc-cross/x86_64-linux-gnu/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/13/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/x86_64-linux-gnu/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin/
libraries: =/usr/lib/gcc-cross/x86_64-linux-gnu/13/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/13/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/x86_64-linux-gnu/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib/:/lib/x86_64-linux-gnu/13/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/13/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/:/lib/:/usr/lib/
# 对于 musl-gcc,由于是原生 gcc 编译器的 wrapper,所以 musl-gcc -print-search-dirs 打印的是原生架构编译器的搜索目录,这是不对的。
# 可以使用如下命令来查看 musl-gcc 实际搜索和链接路径。
root@ubuntu:~# echo 'int main(void){}' >main.c
root@ubuntu:~# musl-gcc -g -v -Wl,-v main.c |& tail -6
gcc 提供的头文件 /usr/include/aarch64-linux-gnu/ 系统标准库 /usr/lib/aarch64-linux-gnu/
fedora 40 安装交叉静态编译工具链 #
类似的,以 fedora40 x86_64 编译环境为例(CentOS、RHEL 类似),如果要编译出 arm64 架构的静态二进制,需要先安装 gcc-aarch64 编译工具链,在 go build 时使用 CC 环境变量来指定使用该工具链。
同时还要安装 sysroot-aarch64-fc40-glibc 包,它为交叉编译提供了 glibc 标准库头文件和静态 glibc 库支持(大坑,卡了很久才解决),通过 CGO_LDFLAGS 环境变量的 –sysroot 参数传递给交叉编译工具链,参考:
- https://cloud.tencent.com/developer/article/1851733
- https://stackoverflow.com/questions/77462229/cross-compile-from-centos-7-to-raspberry-pi-2b-cant-get-libc-and-system-inclu
yum install -y glibc-devel glibc-static gcc
# 在 x86_64 环境中,可以直接编译出 x86_64 架构二进制
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags 'osusergo netgo sqlite_omit_load_extension no_dynamic_plugins' -o myagent -ldflags=" -extldflags '-static'" ./cmd/
# 交叉编译 arm64 架构的二进制失败,这是由于 x86_64 gcc 不支持汇编 arm64 指令
[root@d8e784ed1d22 bp-agent]# GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -tags 'osusergo netgo sqlite_omit_load_extension no_dynamic_plugins' -buildmode=exe -o bp-agent -ldflags=" -extldflags '-static'" ./cmd/
# runtime/cgo
gcc_arm64.S: Assembler messages:
gcc_arm64.S:30: Error: no such instruction: `stp x29,x30,[sp,'
gcc_arm64.S:34: Error: operand size mismatch for `mov'
gcc_arm64.S:36: Error: no such instruction: `stp x19,x20,[sp,'
gcc_arm64.S:39: Error: no such instruction: `stp x21,x22,[sp,'
gcc_arm64.S:42: Error: no such instruction: `stp x23,x24,[sp,'
gcc_arm64.S:45: Error: no such instruction: `stp x25,x26,[sp,'
gcc_arm64.S:48: Error: no such instruction: `stp x27,x28,[sp,'
gcc_arm64.S:52: Error: operand size mismatch for `mov'
gcc_arm64.S:53: Error: operand size mismatch for `mov'
gcc_arm64.S:54: Error: operand size mismatch for `mov'
gcc_arm64.S:56: Error: no such instruction: `blr x20'
gcc_arm64.S:57: Error: no such instruction: `blr x19'
gcc_arm64.S:59: Error: no such instruction: `ldp x27,x28,[sp,'
gcc_arm64.S:62: Error: no such instruction: `ldp x25,x26,[sp,'
gcc_arm64.S:65: Error: no such instruction: `ldp x23,x24,[sp,'
gcc_arm64.S:68: Error: no such instruction: `ldp x21,x22,[sp,'
gcc_arm64.S:71: Error: no such instruction: `ldp x19,x20,[sp,'
gcc_arm64.S:74: Error: no such instruction: `ldp x29,x30,[sp],'
# 安装支持交叉编译的 arm64 架构的编译工具链
yum install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu gcc-c++-aarch64-linux-gnu;
# 编译失败,提示找不到 stdlib.h 库
[root@d8e784ed1d22 bp-agent]# CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -tags 'osusergo netgo sqlite_omit_load_extension no_dynamic_plugins' -o myagent -ldflags=" -extldflags '-static'" ./cmd/
# runtime/cgo
_cgo_export.c:3:10: fatal error: stdlib.h: No such file or directory
3 | #include <stdlib.h>
| ^~~~~~~~~~
compilation terminated.
# 查看 aarch64-linux-gnu-gcc 编译器配置参数
[root@d8e784ed1d22 bp-agent]# aarch64-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=aarch64-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/aarch64-linux-gnu/14/lto-wrapper
Target: aarch64-linux-gnu
Configured with: ../gcc-14.1.1-20240508/configure --bindir=/usr/bin --build=x86_64-redhat-linux-gnu --datadir=/usr/share --disable-decimal-float --disable-dependency-tracking --disable-gold --disable-libgcj --disable-libgomp --disable-libmpx --disable-libquadmath --disable-libssp --disable-libunwind-exceptions --disable-shared --disable-silent-rules --disable-sjlj-exceptions --disable-threads --with-ld=/usr/bin/aarch64-linux-gnu-ld --enable-__cxa_atexit --enable-checking=release --enable-gnu-unique-object --enable-initfini-array --enable-languages=c,c++ --enable-linker-build-id --enable-lto --enable-nls --enable-obsolete --enable-plugin --enable-targets=all --exec-prefix=/usr --host=x86_64-redhat-linux-gnu --includedir=/usr/include --infodir=/usr/share/info --libexecdir=/usr/libexec --localstatedir=/var --mandir=/usr/share/man --prefix=/usr --program-prefix=aarch64-linux-gnu- --sbindir=/usr/sbin --sharedstatedir=/var/lib --sysconfdir=/etc --target=aarch64-linux-gnu --with-bugurl=http://bugzilla.redhat.com/bugzilla/ --with-gcc-major-version-only --with-isl --with-newlib --with-plugin-ld=/usr/bin/aarch64-linux-gnu-ld --with-sysroot=/usr/aarch64-linux-gnu/sys-root --with-system-libunwind --with-system-zlib --without-headers --enable-gnu-indirect-function --with-linker-hash-style=gnu
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 14.1.1 20240507 (Red Hat Cross 14.1.1-1) (GCC)
# --with-sysroot=/usr/aarch64-linux-gnu/sys-root 目录为空:
[root@d8e784ed1d22 bp-agent]# ls /usr/aarch64-linux-gnu/bin/
ar as ld ld.bfd nm objcopy objdump ranlib readelf strip
[root@d8e784ed1d22 bp-agent]# ls /usr/aarch64-linux-gnu/sys-root/
[root@d8e784ed1d22 bp-agent]#
# 安装 aarch64 对应的 sysroot 包 sysroot-aarch64-fc40-glibc.noarch,它提供了交叉编译所需的 glibc 头文件和静态库
[root@d8e784ed1d22 bp-agent]# yum install sysroot-aarch64-fc40-glibc.noarch
[root@d8e784ed1d22 bp-agent]# ls /usr/aarch64-redhat-linux/sys-root/fc40/usr/
include lib lib64
# 使用 CGO_CFLAGS 环境变量为交叉编译 gcc 指定 --sysroot 参数,来使用安装的 aarch64 的 glibc 头文件和静态库,
# 同时使用 CC 环境变量指定使用该工具链来编译 CGO 链接的 C 程序
[root@d8e784ed1d22 bp-agent]# CC=aarch64-linux-gnu-gcc CGO_CFLAGS="-g -O2 --sysroot=/usr/aarch64-redhat-linux/sys-root/fc40" CGO_LDFLAGS="-g -O2 --sysroot=/usr/aarch64-redhat-linux/sys-root/fc40" GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -x -tags 'osusergo netgo sqlite_omit_load_extension no_dynamic_plugins' -o myagent -ldflags=" -extldflags '-static'" ./cmd/
[root@d8e784ed1d22 bp-agent]# file myagent
myagent: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=90ac14fe0ac828b0defb3ae4e3c328a589948a8c, for GNU/Linux 3.7.0, with debug_info, not stripped
[root@d8e784ed1d22 bp-agent]# ldd myagent
not a dynamic executable
Fedora、CentOS 系列,安装交叉编译工具链后,还要安装 sysroot 包才能提供目标架构的头文件、库文件:
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-linux-gnu/
total 0
drwxr-xr-x 1 root root 92 Jan 22 07:59 bin
drwxr-xr-x 1 root root 0 May 13 2024 sys-root
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-linux-gnu/bin/
total 20056
-rwxr-xr-x 2 root root 1286616 May 13 2024 ar
-rwxr-xr-x 2 root root 2351968 May 13 2024 as
-rwxr-xr-x 4 root root 3426184 May 13 2024 ld
-rwxr-xr-x 4 root root 3426184 May 13 2024 ld.bfd
-rwxr-xr-x 2 root root 1362072 May 13 2024 nm
-rwxr-xr-x 2 root root 1514504 May 13 2024 objcopy
-rwxr-xr-x 2 root root 3455728 May 13 2024 objdump
-rwxr-xr-x 2 root root 1286616 May 13 2024 ranlib
-rwxr-xr-x 2 root root 890584 May 13 2024 readelf
-rwxr-xr-x 2 root root 1514504 May 13 2024 strip
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-linux-gnu/sys-root/ # 空目录
total 0
[root@d8e784ed1d22 ~]# rpm -qa |grep sysroot
sysroot-aarch64-fc40-glibc-2.39-33.fc40.noarch
# 安装 sysroot 包后,才会有交叉编译架构的头文件、标准库文件等类似于 /usr 的第二目录结构
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-redhat-linux/
total 0
drwxr-xr-x 1 root root 8 Jan 22 08:52 sys-root
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-redhat-linux/sys-root/
total 0
drwxr-xr-x 1 root root 6 Jan 22 08:52 fc40
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-redhat-linux/sys-root/fc40/
total 0
drwxr-xr-x 1 root root 30 Jan 22 08:52 usr
[root@d8e784ed1d22 ~]# ls -l /usr/aarch64-redhat-linux/sys-root/fc40/usr/
total 0
drwxr-xr-x 1 root root 2090 Jan 22 08:52 include
drwxr-xr-x 1 root root 42 Jan 22 08:52 lib
drwxr-xr-x 1 root root 1234 Jan 22 08:52 lib64
# 在编译时需要为链接器传入 --sysroot 参数来指定上面目录
aarch64-linux-gnu-gcc --sysroot=/usr/aarch64-redhat-linux/sys-root/fc40/
交叉编译 pkg-config #
pkg-config 是一个管理编译和链接参数的工具,可以简化处理外部依赖库的工作。
pkg-config 是架构相关的,需要安装对应目录架构的 pkg-config 包,后续使用 target triplet prefix 来区分:
- pkgconf-pkg-config.x86_64
- pkgconf-pkg-config.aarch64
alizj@lima-dev2:~$ ls -l /usr/bin/pkg-config
lrwxrwxrwx 1 root root 7 Apr 24 2023 /usr/bin/pkg-config -> pkgconf
alizj@lima-dev2:~$ dpkg -S /usr/bin/pkgconf
pkgconf-bin: /usr/bin/pkgconf
alizj@lima-dev2:~$ dpkg -S /usr/bin/aarch64-linux-gnu-pkg-config
pkgconf:arm64: /usr/bin/aarch64-linux-gnu-pkg-config
除了用 -I 和 -L 手动指定路径外,还可以使用一些包提供的 pkg-config 机制和命令来自动查找要链接的库、头文件等编译参数:
- 它使用库开发者发布的 .pc 格式文件来自动生成 -I、-L 和 -l 参数。
- 库开发者使用
pkg-config --cflags --libs xxx
生成 gcc 的 -L 或 -l 参数(可以与 gcc、makefile 或 Autotools、CMake 等集成) - 主流构构建工具,如 CMake/autotools/meson/bazel, 都支持 pkg-config 来配置和管理动态链接库;
系统默认 .pc 文件路径:
alizj@ubuntu:~$ ls /usr/lib/pkgconfig/
alizj@ubuntu:~$ ls /usr/share/pkgconfig/
iso-codes.pc personality.d shared-mime-info.pc systemd.pc udev.pc xkeyboard-config.pc
alizj@ubuntu:~$ ls /usr/local/lib/pkgconfig
ls: cannot access '/usr/local/lib/pkgconfig': No such file or directory
alizj@ubuntu:~$ ls /usr/local/share/pkgconfig
ls: cannot access '/usr/local/share/pkgconfig': No such file or directory
示例:
CFLAGS="`pkg-config --cflags apophenia glib-2.0` -g -Wall -std=gnu11 -O3"
LDLIBS="`pkg-config --libs apophenia glib-2.0`"
# 等效为
CFLAGS="-I/home/b/root/include -g -Wall-std=gnu11 -O3"
LDLIBS="-L/home/b/root/lib -lweirdlib"
# 两者结合使用
gcc `pkg-config --cflags --libs gsl libxml-2.0` -o specific specific.c
# 等效为
gcc -I/usr/include/libxml2 -lgsl -lgslcblas -lm -lxml2 -o specific specific.c
示例: Makefile 中使用 pkg-config,假设依赖 glib 库:
- CC 指定编译器,对于交叉编译,需要使用带前缀架构的编译器,如
x86_64-linux-gnu-gcc
# Compiler and flags
CC = gcc
CFLAGS = -Wall -Iinclude $(shell pkg-config --cflags glib-2.0) # 生成编译所需的参数,如 -Idir
# Directories
SRC_DIR = src
LIB_DIR = lib
EXT_LIB_DIR = ext_lib
# Source files
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
LIB_FILES = $(wildcard $(LIB_DIR)/*.c)
# Object files
OBJ_FILES = $(SRC_FILES:.c=.o) $(LIB_FILES:.c=.o)
# External libraries
EXT_LIBS = -L$(EXT_LIB_DIR) -lotherlib $(shell pkg-config --libs glib-2.0) # 生成链接所需参数,如 -Ldir -llib
# Output executable
OUTPUT = myprogram
# Default target
all: $(OUTPUT)
# Link object files
$(OUTPUT): $(OBJ_FILES)
$(CC) -o $@ $^ $(EXT_LIBS)
# Compile source files
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
# Clean up
clean:
rm -f $(SRC_DIR)/*.o $(LIB_DIR)/*.o $(OUTPUT)
.PHONY: all clean
交叉编译配置参数 autoconf #
对于 autoconf 项目,执行 ./configure 脚本时可以指定交叉编译参数:
- –build=build-type:构建机器的架构类型,缺省使用
config.guess
的结果; - –host=host-type:运行 autoconf 的机器架构类型,确实和 –build 一致;
- –target=target-type:编译生成的二进制的机器架构类型,缺省和 –host 一致;
示例:
# https://www.linuxfromscratch.org/lfs/view/development/chapter05/binutils-pass1.html
../configure --prefix=$LFS/tools \
--with-sysroot=$LFS \
--target=$LFS_TGT \
--disable-nls \
--enable-gprofng=no \
--disable-werror \
--enable-new-dtags \
--enable-default-hash-style=gnu
# --prefix 指定安装路径
./configure --build=i686-pc-linux-gnu --host=m68k-coff --prefix=/path/to/install
make
make install
binutils:汇编器、链接器和二进制工具 #
binutils 是架构相关的,提供了对应架构的二进制工具,它们可以对 ELF 文件进行操作:
- as:汇编器;
- ld/ld.bfd/ld.gold:链接器及链接脚本;
- gprof/gprofng/addr2line
- ar:静态库管理工具
- elfedit/readelf/ranlib
- objcopy/objdump/nm/addr2line
- size/strings/strip
gcc 使用对应架构的 binutils 提供的链接器,来对程序进行链接。
alizj@ubuntu:~$ apt list --installed binutils*
binutils-aarch64-linux-gnu/noble-updates,noble-security,now 2.42-4ubuntu2.3 arm64 [installed,automatic]
binutils-common/noble-updates,noble-security,now 2.42-4ubuntu2.3 arm64 [installed,automatic]
binutils-x86-64-linux-gnu/noble-updates,noble-security,now 2.42-4ubuntu2.3 arm64 [installed,automatic]
binutils/noble-updates,noble-security,now 2.42-4ubuntu2.3 arm64 [installed,automatic]
binutils-dev/noble-updates,noble-security 2.42-4ubuntu2.3 arm64
alizj@ubuntu:~$ dpkg -L binutils | sort
...
/usr/bin/addr2line
/usr/bin/ar
/usr/bin/as
/usr/bin/c++filt
/usr/bin/dwp
/usr/bin/elfedit
/usr/bin/gold
/usr/bin/gp-archive
/usr/bin/gp-collect-app
/usr/bin/gp-display-html
/usr/bin/gp-display-src
/usr/bin/gp-display-text
/usr/bin/gprof
/usr/bin/gprofng
/usr/bin/ld
/usr/bin/ld.bfd
/usr/bin/ld.gold
/usr/bin/nm
/usr/bin/objcopy
/usr/bin/objdump
/usr/bin/ranlib
/usr/bin/readelf
/usr/bin/size
/usr/bin/strings
/usr/bin/strip
# 链接器
/usr/lib/compat-ld
/usr/lib/compat-ld/ld
/usr/lib/gold-ld
/usr/lib/gold-ld/ld
...
alizj@ubuntu:~$ dpkg -L binutils-aarch64-linux-gnu | sort
/.
/usr
/usr/bin
/usr/bin/aarch64-linux-gnu-addr2line
/usr/bin/aarch64-linux-gnu-ar
/usr/bin/aarch64-linux-gnu-as
/usr/bin/aarch64-linux-gnu-c++filt
/usr/bin/aarch64-linux-gnu-dwp
/usr/bin/aarch64-linux-gnu-elfedit
/usr/bin/aarch64-linux-gnu-gold
/usr/bin/aarch64-linux-gnu-gp-archive
/usr/bin/aarch64-linux-gnu-gp-collect-app
/usr/bin/aarch64-linux-gnu-gp-display-html
/usr/bin/aarch64-linux-gnu-gp-display-src
/usr/bin/aarch64-linux-gnu-gp-display-text
/usr/bin/aarch64-linux-gnu-gprof
/usr/bin/aarch64-linux-gnu-gprofng
/usr/bin/aarch64-linux-gnu-ld # 链接器
/usr/bin/aarch64-linux-gnu-ld.bfd # ld 默认指向 bfd,故它为实际的默认链接器
/usr/bin/aarch64-linux-gnu-ld.gold # 因缺少维护,已被废弃
/usr/bin/aarch64-linux-gnu-nm
/usr/bin/aarch64-linux-gnu-objcopy
/usr/bin/aarch64-linux-gnu-objdump
/usr/bin/aarch64-linux-gnu-ranlib
/usr/bin/aarch64-linux-gnu-readelf
/usr/bin/aarch64-linux-gnu-size
/usr/bin/aarch64-linux-gnu-strings
/usr/bin/aarch64-linux-gnu-strip
...
/usr/lib/aarch64-linux-gnu/bfd-plugins
/usr/lib/aarch64-linux-gnu/bfd-plugins/libdep.so
# 各种架构的链接脚本
/usr/lib/aarch64-linux-gnu/ldscripts
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64elf32b.x
...
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64linuxb.xwe
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64linux.x
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64linux.xbn
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64linux.xc
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64linux.xce
/usr/lib/aarch64-linux-gnu/ldscripts/aarch64linux.xd
...
/usr/lib/aarch64-linux-gnu/ldscripts/stamp
...
binutils 提供两种类型链接器: bfd(默认) 和 gold(因缺乏维护,新版本的 binutils 已经去掉了)。
另外两种常用的链接器是 lld 和 mold:
- zed 等 Rust 项目使用 mold:https://github.com/rui314/mold,它的主要优势是比 GNU ld, GNU gold, LLVM lld 的链接速度都快。
- lld 是 LLVM 项目提供的链接器,支持多架构(而 binutils 及它提供的 ld、bfd、gold 都是特定架构):https://github.com/rust-lang/rust/pull/140525
ld 间的链接关系: ld -》aarch64-linux-gnu-ld -》aarch64-linux-gnu-ld.bfd
alizj@lima-dev2:~$ dpkg -S /usr/bin/ld*
binutils: /usr/bin/ld
binutils: /usr/bin/ld.bfd
binutils: /usr/bin/ld.gold
libc-bin: /usr/bin/ld.so
libc-bin: /usr/bin/ldd
alizj@ubuntu:~$ ls -l /usr/bin/ld
lrwxrwxrwx 1 root root 20 Aug 7 18:15 /usr/bin/ld -> aarch64-linux-gnu-ld
alizj@ubuntu:~$ ls -l /usr/bin/aarch64-linux-gnu-ld
lrwxrwxrwx 1 root root 24 Aug 7 18:15 /usr/bin/aarch64-linux-gnu-ld -> aarch64-linux-gnu-ld.bfd
alizj@ubuntu:~$ ls -l /usr/bin/aarch64-linux-gnu-ld.bfd
-rwxr-xr-x 1 root root 1710520 Aug 7 18:15 /usr/bin/aarch64-linux-gnu-ld.bfd
gcc 和 binutils 都提供了 ar/nm 等二进制工具,但前缀不同(前者多了 -gcc 前缀),系统默认使用的是 binutils 提供的版本:
alizj@ubuntu:~$ dpkg -S /usr/bin/aarch64-linux-gnu-*nm*
gcc-aarch64-linux-gnu: /usr/bin/aarch64-linux-gnu-gcc-nm
gcc-13-aarch64-linux-gnu: /usr/bin/aarch64-linux-gnu-gcc-nm-13
binutils-aarch64-linux-gnu: /usr/bin/aarch64-linux-gnu-nm
alizj@ubuntu:~$ which ar
/usr/bin/ar
alizj@ubuntu:~$ ls -l /usr/bin/ar
lrwxrwxrwx 1 root root 20 Aug 7 18:15 /usr/bin/ar -> aarch64-linux-gnu-ar
alizj@ubuntu:~$ dpkg -S /usr/bin/aarch64-linux-gnu-ar
binutils-aarch64-linux-gnu: /usr/bin/aarch64-linux-gnu-ar
ld,bfd、gold、mold、ld.lld,lld 之间关系 #
- ld 是 gnu binutils 包提供的, 是 gcc 和 clang 默认使用的链接器 ,它实际链接到 bfd。
- gold 也是 gnu 提供的,但是已经废弃。
- lld 是 LLVM 提供的。
- ld.lld 是到 lld 的链接,等效于 lld -flavor gnu,是通过 gcc -fuse-ld=lld 时实际调用的二进制。
- mold 是开源链接器,主要优势是速度快:gcc/clang -fuse-ld=mold 或 clang -fuse-ld=/path/to/mold 来使用。
gcc 和 clang 的默认链接器都是 ld,需要设置 -fuse-ld=lld 来使用支持多架构的 lld。
ld/bfd/gold/mold 都是特定架构一个二进制,而 lld 和 clang 类似,天然支持多架构:
root@ubuntu:/Users/alizj/code/rust/my-demo# ls -l /usr/bin/ld
lrwxrwxrwx 1 root root 20 Mar 10 21:52 /usr/bin/ld -> aarch64-linux-gnu-ld
root@ubuntu:/Users/alizj/code/rust/my-demo# ls -l /usr/bin/aarch64-linux-gnu-ld
lrwxrwxrwx 1 root root 24 Mar 10 21:52 /usr/bin/aarch64-linux-gnu-ld -> aarch64-linux-gnu-ld.bfd
root@ubuntu:/Users/alizj/code/rust/my-demo# ls -l /usr/bin/lld
lrwxrwxrwx 1 root root 6 Mar 30 2024 /usr/bin/lld -> lld-18
root@ubuntu:/Users/alizj/code/rust/my-demo# ls -l /usr/bin/lld-18
lrwxrwxrwx 1 root root 22 May 27 2024 /usr/bin/lld-18 -> ../lib/llvm-18/bin/lld
root@ubuntu:/Users/alizj/code/rust/my-demo# ls -l /usr/lib/llvm-18/bin/*lld*
lrwxrwxrwx 1 root root 3 May 27 2024 /usr/lib/llvm-18/bin/ld64.lld -> lld
lrwxrwxrwx 1 root root 3 May 27 2024 /usr/lib/llvm-18/bin/ld.lld -> lld
-rwxr-xr-x 1 root root 5121920 May 27 2024 /usr/lib/llvm-18/bin/lld
lrwxrwxrwx 1 root root 3 May 27 2024 /usr/lib/llvm-18/bin/lld-link -> lld
libc 库和动态链接器 #
libc 库封装了所有 POSIX 系统调用,是用户程序与 Kernel 的接口,在编译 glibc 时需要提供对应版本的内核头文件。
libc 库有多种实现类型:
- glibc:通用;
- musl libc:也是通用的 libc,但是更轻量,在轻量化容器镜像中得到广泛应用:https://musl.libc.org/
- uClibc/uClibc-ng:嵌入式优化的 libc 库:https://uclibc-ng.org/
- newlib:嵌入式优化的 libc 库:https://sourceware.org/newlib/
Ubunut 24.04 glibc 相关的包:
- libc-bin:提供 ldd 动态库链接器,locale、tzselect 等国际化支持;
- libc-dev-bin:仅提供 gencat 命令;
- libc-devtools:提供 memusage、mtrace、sprof 等不常用的内存分析命令;
- libc6-dbg: libc 库 debuginfo 调试符号;
- libc6-dev: libc 库标准头文件和 libc.a 静态库文件;
- libc6:glibc 库;
- linux-libc-dev:提供给 libc 使用的内核头文件
libc 提供了 ELF 运行时动态链接器 ld-linux-
如果手动编译和打包 glibc,则需要使用 patchelf
工具来修改 ELF 文件的动态链接库加载器路径:
sudo apt install -y patchelf
patchelf --set-interpreter $(pwd)/glibc-2.31-target/lib/ld-linux-x86-64.so.2 ./node-v18.12.0-linux-x64/bin/node
alizj@ubuntu:~$ sudo apt list |grep libc |grep installed |grep -v x86 |grep -v amd64
glibc-source/noble-updates,noble-security,noble-updates,noble-security,now 2.39-0ubuntu8.3 all [installed]
libc-bin/noble-updates,noble-security,now 2.39-0ubuntu8.3 arm64 [installed]
libc-dev-bin/noble-updates,noble-security,now 2.39-0ubuntu8.3 arm64 [installed,automatic]
libc-devtools/noble-updates,noble-security,now 2.39-0ubuntu8.3 arm64 [installed,automatic]
libc6-dbg/noble-updates,noble-security,now 2.39-0ubuntu8.3 arm64 [installed]
libc6-dev/noble-updates,noble-security,now 2.39-0ubuntu8.3 arm64 [installed,automatic]
libc6/noble-updates,noble-security,now 2.39-0ubuntu8.3 arm64 [installed]
linux-libc-dev/noble-updates,noble-security,now 6.8.0-52.53 arm64 [installed,automatic]
glibc 是架构相关的,位于 /usr/lib/
glibc 标准头文件位于 /usr/include/
root@lima-dev2:/home/alizj.linux# dpkg -L libc6
# libc 动态链接器配置
/etc/ld.so.conf.d
/etc/ld.so.conf.d/aarch64-linux-gnu.conf
# 架构相关的 libc 库目录
/lib/aarch64-linux-gnu
/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 #动态链接器
/lib/aarch64-linux-gnu/libBrokenLocale.so.1
/lib/aarch64-linux-gnu/libanl.so.1
# GNU glibc 库
/lib/aarch64-linux-gnu/libc.so.6
/lib/aarch64-linux-gnu/libc_malloc_debug.so.0
/lib/aarch64-linux-gnu/libdl.so.2
/lib/aarch64-linux-gnu/libm.so.6
/lib/aarch64-linux-gnu/libmemusage.so
/lib/aarch64-linux-gnu/libmvec.so.1
/lib/aarch64-linux-gnu/libnsl.so.1
/lib/aarch64-linux-gnu/libnss_compat.so.2
/lib/aarch64-linux-gnu/libnss_dns.so.2
/lib/aarch64-linux-gnu/libnss_files.so.2
/lib/aarch64-linux-gnu/libnss_hesiod.so.2
/lib/aarch64-linux-gnu/libpcprofile.so
/lib/aarch64-linux-gnu/libpthread.so.0
/lib/aarch64-linux-gnu/libresolv.so.2
/lib/aarch64-linux-gnu/librt.so.1
/lib/aarch64-linux-gnu/libthread_db.so.1
/lib/aarch64-linux-gnu/libutil.so.1
alizj@lima-dev2:~$ dpkg -L libc6-dev
...
/usr/include/aarch64-linux-gnu/a.out.h
/usr/include/aarch64-linux-gnu/bits
/usr/include/aarch64-linux-gnu/bits/a.out.h
/usr/include/aarch64-linux-gnu/bits/argp-ldbl.h
/usr/include/aarch64-linux-gnu/bits/atomic_wide_counter.h
/usr/include/aarch64-linux-gnu/bits/wchar2.h
/usr/include/aarch64-linux-gnu/bits/wctype-wchar.h
/usr/include/aarch64-linux-gnu/bits/wordsize.h
/usr/include/aarch64-linux-gnu/bits/xopen_lim.h
/usr/include/aarch64-linux-gnu/fpu_control.h
/usr/include/aarch64-linux-gnu/gnu
/usr/include/aarch64-linux-gnu/gnu/lib-names-lp64.h
/usr/include/aarch64-linux-gnu/gnu/lib-names.h
/usr/include/aarch64-linux-gnu/gnu/libc-version.h
/usr/include/aarch64-linux-gnu/gnu/stubs-lp64.h
/usr/include/aarch64-linux-gnu/gnu/stubs.h
/usr/include/aarch64-linux-gnu/ieee754.h
/usr/include/aarch64-linux-gnu/sys
/usr/include/aarch64-linux-gnu/sys/acct.h
/usr/include/aarch64-linux-gnu/sys/auxv.h
/usr/include/aarch64-linux-gnu/sys/bitypes.h
/usr/include/aarch64-linux-gnu/sys/cdefs.h
/usr/include/aarch64-linux-gnu/sys/dir.h
#...
/usr/include/aio.h
/usr/include/aliases.h
/usr/include/alloca.h
/usr/include/fcntl.h
/usr/include/features-time64.h
/usr/include/features.h
/usr/include/wchar.h
/usr/include/wctype.h
/usr/include/wordexp.h
...
/usr/lib/aarch64-linux-gnu/Mcrt1.o
/usr/lib/aarch64-linux-gnu/Scrt1.o
/usr/lib/aarch64-linux-gnu/audit
/usr/lib/aarch64-linux-gnu/audit/sotruss-lib.so
/usr/lib/aarch64-linux-gnu/crt1.o
/usr/lib/aarch64-linux-gnu/crti.o
/usr/lib/aarch64-linux-gnu/crtn.o
/usr/lib/aarch64-linux-gnu/gcrt1.o
/usr/lib/aarch64-linux-gnu/grcrt1.o
/usr/lib/aarch64-linux-gnu/libBrokenLocale.a
/usr/lib/aarch64-linux-gnu/libanl.a
/usr/lib/aarch64-linux-gnu/libc.a # glibc 静态库和动态库
/usr/lib/aarch64-linux-gnu/libc.so
/usr/lib/aarch64-linux-gnu/libc_nonshared.a
/usr/lib/aarch64-linux-gnu/libdl.a
/usr/lib/aarch64-linux-gnu/libg.a
/usr/lib/aarch64-linux-gnu/libm-2.38.a
/usr/lib/aarch64-linux-gnu/libm.a
/usr/lib/aarch64-linux-gnu/libm.so
/usr/lib/aarch64-linux-gnu/libmcheck.a
/usr/lib/aarch64-linux-gnu/libmvec.a
/usr/lib/aarch64-linux-gnu/libpthread.a
/usr/lib/aarch64-linux-gnu/libpthread_nonshared.a
/usr/lib/aarch64-linux-gnu/libresolv.a
/usr/lib/aarch64-linux-gnu/librt.a
/usr/lib/aarch64-linux-gnu/libutil.a
/usr/lib/aarch64-linux-gnu/rcrt1.o
/usr/lib/aarch64-linux-gnu/libBrokenLocale.so
/usr/lib/aarch64-linux-gnu/libanl.so
/usr/lib/aarch64-linux-gnu/libc_malloc_debug.so
/usr/lib/aarch64-linux-gnu/libmvec.so
/usr/lib/aarch64-linux-gnu/libnss_compat.so
/usr/lib/aarch64-linux-gnu/libnss_hesiod.so
/usr/lib/aarch64-linux-gnu/libresolv.so
/usr/lib/aarch64-linux-gnu/libthread_db.so
root@lima-dev2:/home/alizj.linux# dpkg -L libc-bin
/.
/etc
/etc/bindresvport.blacklist
/etc/gai.conf
/etc/ld.so.conf
/etc/ld.so.conf.d
/etc/ld.so.conf.d/libc.conf # libc 动态库加载配置
/sbin
/sbin/ldconfig # 动态加载配置命令
/sbin/ldconfig.real
/usr
/usr/bin
/usr/bin/getconf
/usr/bin/getent
/usr/bin/iconv
/usr/bin/ldd # 打印动态库对象的依赖
/usr/bin/locale # 国际化
/usr/bin/localedef
/usr/bin/pldd
/usr/bin/tzselect
/usr/bin/zdump
/usr/lib/locale # locale 数据库
/usr/lib/locale/C.utf8
/usr/lib/locale/C.utf8/LC_ADDRESS
/usr/lib/locale/C.utf8/LC_COLLATE
/usr/lib/locale/C.utf8/LC_CTYPE
/var/cache/ldconfig # 动态链接器缓存
/usr/bin/ld.so # 动态链接器
linux-libc-dev: 内核导出到用户空间的头文件(也可以通过 make headers_install
来从内核头文件源码来安装)
root@lima-dev2:/home/alizj.linux# dpkg -L linux-libc-dev
/.
/usr
/usr/include
/usr/include/aarch64-linux-gnu # 架构相关的头文件
/usr/include/aarch64-linux-gnu/asm
/usr/include/aarch64-linux-gnu/asm/auxvec.h
/usr/include/aarch64-linux-gnu/asm/bitsperlong.h
/usr/include/aarch64-linux-gnu/asm/bpf_perf_event.h
/usr/include/aarch64-linux-gnu/asm/byteorder.h
/usr/include/aarch64-linux-gnu/asm/errno.h
/usr/include/aarch64-linux-gnu/asm/fcntl.h
/usr/include/aarch64-linux-gnu/asm/hwcap.h
/usr/include/aarch64-linux-gnu/asm/ioctl.h
/usr/include/aarch64-linux-gnu/asm/ioctls.h
/usr/include/aarch64-linux-gnu/asm/ipcbuf.h
#...
/usr/include/aarch64-linux-gnu/asm/unistd.h
/usr/include/asm-generic # 架构无关(通用)的头文件
/usr/include/asm-generic/auxvec.h
/usr/include/asm-generic/bitsperlong.h
/usr/include/asm-generic/bpf_perf_event.h
#...
/usr/include/asm-generic/unistd.h
/usr/include/drm
/usr/include/drm/amdgpu_drm.h
/usr/include/drm/armada_drm.h
/usr/include/drm/drm.h
/usr/include/linux # 通用内核头文件
/usr/include/linux/acct.h
/usr/include/linux/xilinx-v4l2-controls.h
/usr/include/linux/zorro.h
/usr/include/linux/zorro_ids.h
#...
编译 glibc 时依赖上面安装的内核头文件:
--enable-kernel=4.14
: 告诉 glibc 库支持 4.14 及以后的内核;--with-headers=$LFS/usr/include
: 告诉 glibc 库安装的内核头文件目录,glibc 根据内核支持的特性进行对应优化;
# https://www.linuxfromscratch.org/lfs/view/12.0/chapter05/glibc.html
../configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(../scripts/config.guess) \
--enable-kernel=4.14 \
--with-headers=$LFS/usr/include \
libc_cv_slibdir=/usr/lib
编译和使用 glibc #
参考:https://www.rectcircle.cn/posts/linux-build-once-run-anywhere/
如果主程序依赖一个特定的 glibc 版本,则可以进行手动编译打包该 glibc,然后随主程序一起打包:
- 下载合适版本的 glibc 源码,进行编译,生成对应版本的 glibc 库;
- 编译时指定
-nostdlib -L /path/to/your/glibc
参数,告诉 gcc 不使用(查找)交叉编译工具链关联的 glibc 库,但使用 -L 指定上一步生成的 glibc 库目录。
wget http://ftp.gnu.org/gnu/glibc/glibc-2.31.tar.gz
tar -zxvf glibc-2.31.tar.gz
glibc_prefix=$(pwd)/glibc-2.31-target
cd glibc-2.31/
rm -rf build && mkdir -p build && cd build
sudo apt update && sudo apt install -y gcc make gdb texinfo gawk bison sed python3-dev python3-pip
../configure --prefix=$glibc_prefix
make -j4
make install
cd ../../
cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 ./glibc-2.31-target/lib
ln -s libstdc++.so.6.0.28 ./glibc-2.31-target/lib/libstdc++.so.6
cp /lib/x86_64-linux-gnu/libgcc_s.so.1 ./glibc-2.31-target/lib
# 压缩
tar -czvf glibc-2.31-target.tar.gz glibc-2.31-target/
由于动态链接器(如 ld-linux-x86-64.so.2,是架构相关的)也是 glibc 提供的,而且路径也被写入到 ELF 中,所以需要修改主程序 ELF 文件中的动态链接器路径,指定为上面打包编译的 glibc 中的动态链接器。
可以使用 patchelf --set-interpreter
命令来修改 ELF 文件的动态链接库加载器路径:
- 另一个可选的工具是 polyfill-glibc
sudo apt install -y patchelf
patchelf --set-interpreter $(pwd)/glibc-2.31-target/lib/ld-linux-x86-64.so.2 ./node-v18.12.0-linux-x64/bin/node
在运行时,使用 LD_LIBRARY_PATH
环境变量来为动态链接器指定打包的 glibc 目录。
glibc 符号版本化 #
动态链接 glibc 库的常见问题是版本兼容性:各 Linux 发行版一般只能保证大版本的 glibc 兼容,如 CentOS 7.9 和 7.2 的 glibc 是兼容的,但是 CentOS 8.1 和 CentOS 7.9 的 glibc 不一定兼容,也就是在较新 glibc 版本的机器上构建出的二进制可能不能在低版本 glibc 机器上运行(升级内核不会有这个问题),报错:
#ldd /tmp/myagent
/tmp/myagent: /lib64/libc.so.6: version `GLIBC_2.33' not found (required by /tmp/myagent)
/tmp/myagent: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by /tmp/myagent)
/tmp/myagent: /lib64/libc.so.6: version `GLIBC_2.32' not found (required by /tmp/myagent)
/tmp/myagent: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /tmp/myagent)
linux-vdso.so.1 => (0x00007fff931d2000)
libc.so.6 => /lib64/libc.so.6 (0x00007f6ee76e9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6ee7ac4000)
这是由于 glibc 使用链接器的 version script
机制(参考:20250124-gnu-ld-manual.md)实现了符号的版本化。
在加载程序 ELF 阶段,动态链接器(如 /lib64/ld-linux-x86-64.so.2)会进行符号版本检查,当不支持时直接报错。
- glibc 的 ld version scripts 可以查看 glibc 符号的版本:
- 各目录下的 Versions 文件,如:https://github.com/bminor/glibc/blob/master/string/Versions
- 可以使用
ldd
、readelf --version-info
、readelf --dyn-syms
等参数查看可执行文件依赖的动态库,找到其中最大的版本 。则这个版本就是该可执行文件依赖的 glibc 的最小版本号。 - glibc 2.34 的一个重大变化,即:
-lpthread, -ldl, -lutil, -lanl, -lresolv
等的符号,已经被移动到 libc.so.6 中。因此,在使用这些库函数的项目编译时,在 2.34 之后,通过 ldd 将只能看到 libc.so.6 的依赖。
glibc 的动态链接问题 #
https://www.rectcircle.cn/posts/linux-c-static-compile/
glibc 即默认情况下不支持静态链接,主要原因是:
- glibc 的一些实现是依赖其他动态链接库实现的,比如 NSS, gconv, IDN 以及 thread cancellation(通过 dlopen 方式,所以 ldd 命令看不到,但是源码可以看出来,如:
libnss
相关)。 - 这些动态链接库又声明了对 glibc 的依赖,这样就造成了循环依赖。比如,静态编译了 glibc,由于 glibc 依赖了 libnss3.so(sudo ldconfig -p | grep nss),而 ldd /usr/lib/x86_64-linux-gnu/libnss3.so,此时我们的程序还是会加载一个 glibc 的动态链接库。
这造成两个问题:
- 我们的程序间接依赖 libnss3.so 的动态链接库,且 ldd 也看不到,这与我们想要的静态链接,无 .so 依赖背道而驰。
- 在运行时,同一个 glibc 函数/全局变量在两个地方都有是实现,一个是静态编译的,一个是通过类似 libnss3.so 间接引入的 libc.so.6(即 glibc),这可能带来并发问题。
因此,在发布于 2018 的 glibc 2.27 中,在编译阶段如果指定了静态链接,就会出现警告(只要使用到了 ldopen 之类的函数都会报该问题)。
比如,下面一份来自 Go 标准库中 os/user 的一份 cgo 代码的部分。
#define _GNU_SOURCE
#include <pwd.h>
static int mygetpwuid_r(int uid, struct passwd *pwd,
char *buf, size_t buflen, struct passwd **result) {
return getpwuid_r(uid, pwd, buf, buflen, result);
}
void main()
{}
在 debian 11 中,通过 gcc main.c -static 静态编译。将出现如下警告:
/usr/bin/ld: /tmp/ccB7Eh74.o: in function `mygetpwuid_r':
main.c:(.text+0x34): 警告:Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
我们观察 a.out 的动态链接库情况 ldd a.out 可以发现输出如下:
不是动态可执行文件
但是实际上执行 a.out 会隐式的依赖 libnss3.so 和 libc.so.6(即 glibc)。这比动态链接编译的程序还要糟糕。因为该程序隐藏了其依赖,所以该警告必须要消除。
注意:glibc 的 FAQ 给出了一种解决方案是,在编译 glibc 时,通过 --enable-static-nss
将其依赖的 NSS 也静态编译,但是官方并不推荐。
解决上述 glibc 问题,最好的办法就是使用 musl-libc,它专门为静态链接而设计。
C/Rust 程序动态链接和静态链接 #
对于 Linux ELF 可执行程序,执行的过程如下:
- 父进程执行 fork 系统调用,生成一个子进程,接着在子进程中运行 execve 函数指定的 elf 二进制程序( Linux中执行二进制程序最终都是通过 execve 这个库函数进行的),execve 会调用系统调用把 elf 文件 load 到内存中的代码段(_text)中。
- 如果有依赖的动态链接库,会调用动态链接器进行库文件的地址映射,动态链接库的内存空间是被多个进程共享的。
- 内核从 ELF 文件头得到
_start
的地址,调度执行流从_start
指向的地址开始执行,执行流在_start
执行的代码段中跳转到 libc 中的公共初始化代码段__libc_start_main
,进行程序运行前的初始化工作。 __libc_start_main
的执行过程中,会跳转到_init
中全局变量的初始化工作,随后调用我们的main
函数,进入到主函数的指令流程。- 当
main
函数返回后,跳转到 libc 的公共结束化代码段;
C/C++ 和 Rust 程序一般都需要动态链接 gcc 库和 glibc 库,它们提供了 C Runtime routine 和内存分配、网络和文件 IO 等功能。
编译时使用 -v -Wl,-v main.c
来打印调用链接器 ld 的命令和参数:
- 动态链接器:-dynamic-linker /lib/ld-linux-aarch64.so.1
- 由 libc 库提供。
- /usr/lib/aarch64-linux-gnu 下的 Scrt1.o,crti.o,crtn.o, libc.so:
- libc 库提供的 C Runtime routine。
- -L:/usr/lib/gcc/aarch64-linux-gnu/13,/usr/lib/aarch64-linux-gnu,/usr/lib, /lib:
- libc 库和 gcc 库搜索路径
- /usr/lib/gcc/aarch64-linux-gnu/13 下的 crtbeginS.o,libgcc.so,libgcc_eh.so,crtendS.o:
- gcc 库提供的异常处理、栈展开和 C runtime routine。
注:-v -Wp,-v -Wa,-v -Wl,-v
使用该参数可以打印预处理、编译、汇编、链接各命令的参数。
root@ubuntu:~# echo 'int main(void){}' >main.c
root@ubuntu:~# gcc -g -v -Wl,-v main.c
#...
/usr/bin/ld -plugin /usr/libexec/gcc/aarch64-linux-gnu/13/liblto_plugin.so \
-plugin-opt=/usr/libexec/gcc/aarch64-linux-gnu/13/lto-wrapper -plugin-opt=-fresolution=/tmp/ccQXZAKI.res -plugin-opt=-pass-through=-lgcc \
-plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s \
--build-id \
--eh-frame-hdr --hash-style=gnu \
--as-needed -dynamic-linker /lib/ld-linux-aarch64.so.1 -X -EL -maarch64linux --fix-cortex-a53-843419 -pie -z now -z relro \
/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/Scrt1.o \
/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/crti.o \
/usr/lib/gcc/aarch64-linux-gnu/13/crtbeginS.o \
-L/usr/lib/gcc/aarch64-linux-gnu/13 \
-L/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu \
-L/usr/lib/gcc/aarch64-linux-gnu/13/../../../../lib \
-L/lib/aarch64-linux-gnu -L/lib/../lib \
-L/usr/lib/aarch64-linux-gnu \
-L/usr/lib/../lib \
-L/usr/lib/gcc/aarch64-linux-gnu/13/../../.. \
-v /tmp/cchzaQZo.o \
-lgcc \
--push-state --as-needed -lgcc_s --pop-state \
-lc -lgcc \
--push-state --as-needed -lgcc_s --pop-state \
/usr/lib/gcc/aarch64-linux-gnu/13/crtendS.o \
/usr/lib/gcc/aarch64-linux-gnu/13/../../../aarch64-linux-gnu/crtn.o
对于纯 Rust 代码项目(项目中没有使用 FFI 链接 C/C++ 代码),在生成 *-linux-gnu target
的二进制时,也会动态链接到 libgcc_s.so 和 libc.so 库:
root@ubuntu:/Users/alizj/code/rust/my-demo# uname -a
Linux ubuntu 6.13.7-orbstack-00283-g9d1400e7e9c6 #104 SMP Mon Mar 17 06:15:48 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux
root@ubuntu:/Users/alizj/code/rust/my-demo# cargo build
root@ubuntu:/Users/alizj/code/rust/my-demo# ldd target/debug/my-demo
linux-vdso.so.1 (0x0000ffffa1dab000)
libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ffffa1cb0000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffa1af0000)
/lib/ld-linux-aarch64.so.1 (0x0000ffffa1d60000)
总结:即使对于非常简单空 C/C++/Rust 程序,在编译链接时也会动态链接到 gcc 和 libc 库。
为了避免动态链接 gcc 和 glibc 库,可以使用 musl 库,它提供了 C Runtime routine、libc 库等的静态实现,这样可以实现 C/C++/Rust 程序的静态链接。
但由于 C、C++、Rust 程序除依赖 libc 库外,可能还会依赖其它三方库,所以即使使用 musl 库也不能保证是 100% 静态链接的。
另外,虽然 clang、rustc 支持多架构,但是它们并不包含目标架构的 C Runtime routine、libc 库和头文件等,所以还是需要对应架构的交叉编译工具链。
musl #
为了避免生成的二进制程序动态链接 gcc 和 glibc 库,可以使用 musl 库,它提供了 C Runtime routine、libc 库等的静态实现,这样可以实现 C/C++/Rust 程序的静态链接。
musl 是 libc 标准库的轻量化实现,遵循 POSIX 标准,它通常被认为是静态链接的更好选择:
- 体积更小,可执行文件更加精简: musl 的设计目标就是轻量化、精简实现,因此在静态链接后,二进制的体积普遍比 glibc 小很多。官方 Alpine 镜像(基于 musl)只有几 MB,而 Debian/Ubuntu 等(基于 glibc)的基础镜像往往达到几十到上百 MB。
- 依赖更少,部署更稳定:musl 的 API 与实现都相对简洁,不依赖繁多的动态加载机制,所以在静态链接场景下几乎不需要再额外维护任何兼容层或配置。
- 简单、可控的实现,利于安全审计:glibc 的代码体量庞大、历史包袱和兼容性逻辑也更多,相较之下 musl 的实现较为精简,更易于在特定需求下进行审计和维护。
- 容器与嵌入式场景需求:静态链接+小体积库特别适合容器微服务、IoT 以及嵌入式系统,这些场景通常要求资源使用最小化、系统可移植性和简洁度更高。
root@ubuntu:~# ls -l /usr/lib/aarch64-linux-musl/
total 3484
-rw-r--r-- 1 root root 5680 Nov 11 2023 crt1.o
-rw-r--r-- 1 root root 2816 Nov 11 2023 crti.o
-rw-r--r-- 1 root root 2760 Nov 11 2023 crtn.o
-rw-r--r-- 1 root root 2701670 Nov 11 2023 libc.a
-rwxr-xr-x 1 root root 788960 Nov 11 2023 libc.so
-rw-r--r-- 1 root root 8 Nov 11 2023 libdl.a
-rw-r--r-- 1 root root 8 Nov 11 2023 libm.a
-rw-r--r-- 1 root root 8 Nov 11 2023 libpthread.a
-rw-r--r-- 1 root root 8 Nov 11 2023 libresolv.a
-rw-r--r-- 1 root root 8 Nov 11 2023 librt.a
-rw-r--r-- 1 root root 8 Nov 11 2023 libutil.a
-rw-r--r-- 1 root root 8 Nov 11 2023 libxnet.a
-rw-r--r-- 1 root root 765 Nov 11 2023 musl-gcc.specs
-rw-r--r-- 1 root root 13576 Nov 11 2023 rcrt1.o
-rw-r--r-- 1 root root 5688 Nov 11 2023 Scrt1.o
musl 项目只提供 libc 静态库和头文件等,并不提供自己的编译器和链接器,还需要使用 gcc、clang、rustc 等编译器,并配置相关的编译、链接参数,来实现编译、链接 musl 库。
为了避免人工配置复杂的编译器参数,musl 提供 gcc 和 clang 编译器的 wrapper 脚本 aarch64-linux-musl-gcc、musl-gcc
, 它们内部通过使用自定义的 gcc spec 来静态链接对应的 musl libc 库。
# 脚本
root@ubuntu:/home/alizj/musl-cross-make# file /usr/bin/aarch64-linux-musl-gcc
/usr/bin/aarch64-linux-musl-gcc: POSIX shell script, ASCII text executable
# wrapper 封装了 aarch64-linux-gnu-gcc,使用自定义 musl-gcc.specs
root@ubuntu:/home/alizj/musl-cross-make# cat /usr/bin/aarch64-linux-musl-gcc
#!/bin/sh
exec "${REALGCC:-aarch64-linux-gnu-gcc}" "$@" -specs "/usr/lib/aarch64-linux-musl/musl-gcc.specs"
musl-gcc.specs 文件:
root@ubuntu:/home/alizj/musl-cross-make# cat /usr/lib/aarch64-linux-musl/musl-gcc.specs
%rename cpp_options old_cpp_options
*cpp_options:
-nostdinc -isystem /usr/include/aarch64-linux-musl -isystem include%s %(old_cpp_options)
*cc1:
%(cc1_cpu) -nostdinc -isystem /usr/include/aarch64-linux-musl -isystem include%s
*link_libgcc:
-L/usr/lib/aarch64-linux-musl -L .%s
*libgcc:
libgcc.a%s %:if-exists(libgcc_eh.a%s)
*startfile:
%{shared:;static-pie:/usr/lib/aarch64-linux-musl/rcrt1.o; :/usr/lib/aarch64-linux-musl/Scrt1.o} /usr/lib/aarch64-linux-musl/crti.o crtbeginS.o%s
*endfile:
crtendS.o%s /usr/lib/aarch64-linux-musl/crtn.o
*link:
-dynamic-linker /lib/ld-musl-aarch64.so.1 -nostdlib %{shared:-shared} %{static:-static} %{static-pie:-static -pie --no-dynamic-linker} %{rdynamic:-export-dynamic}
*esp_link:
*esp_options:
*esp_cpp_options:
后续使用 musl库时,必须使用该 wrapper 脚本的 musl-gcc 或 aarch64-linux-musl-gcc,这时才能正确查找和使用 musl 的头文件、库文件:
root@ubuntu:~# echo 'int main(void){}' >main.c
root@ubuntu:~# musl-gcc -g -v -Wp,-v -Wa,-v -Wl,-v main.c
#...
Target: aarch64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 13.3.0-6ubuntu2~24.04' --with-bugurl=file:///usr/share/doc/gcc-13/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-13 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/libexec --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-libstdcxx-backtrace --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-offload-targets=nvptx-none=/build/gcc-13-Nz4ro4/gcc-13-13.3.0/debian/tmp-nvptx/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
#...
/usr/bin/ld -plugin /usr/libexec/gcc/aarch64-linux-gnu/13/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/aarch64-linux-gnu/13/lto-wrapper -plugin-opt=-fresolution=/tmp/ccMxhIBo.res -plugin-opt=-pass-through=/usr/lib/gcc/aarch64-linux-gnu/13/libgcc.a -plugin-opt=-pass-through=/usr/lib/gcc/aarch64-linux-gnu/13/libgcc_eh.a -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=/usr/lib/gcc/aarch64-linux-gnu/13/libgcc.a -plugin-opt=-pass-through=/usr/lib/gcc/aarch64-linux-gnu/13/libgcc_eh.a -dynamic-linker /lib/ld-musl-aarch64.so.1 -nostdlib -pie -z now -z relro /usr/lib/aarch64-linux-musl/Scrt1.o /usr/lib/aarch64-linux-musl/crti.o /usr/lib/gcc/aarch64-linux-gnu/13/crtbeginS.o -L/usr/lib/aarch64-linux-musl -L /usr/lib/gcc/aarch64-linux-gnu/13/. -v /tmp/ccJ0mCRI.o /usr/lib/gcc/aarch64-linux-gnu/13/libgcc.a /usr/lib/gcc/aarch64-linux-gnu/13/libgcc_eh.a -lc /usr/lib/gcc/aarch64-linux-gnu/13/libgcc.a /usr/lib/gcc/aarch64-linux-gnu/13/libgcc_eh.a /usr/lib/gcc/aarch64-linux-gnu/13/crtendS.o /usr/lib/aarch64-linux-musl/crtn.o
GNU ld (GNU Binutils for Ubuntu) 2.42
COLLECT_GCC_OPTIONS='-g' '-v' '-specs=/usr/lib/aarch64-linux-musl/musl-gcc.specs' '-mlittle-endian' '-mabi=lp64' '-dumpdir' 'a.'
从上面的 musl-gcc 输出的 ld 参数可以看出,它静态链接了 musl 提供的 C Runtime routine、gcc 和 libc 库,所以可以实现程序的静态链接:
- musl-gcc 给系统原生 gcc 传递了使用自定义 spec 的参数:–specs=/usr/lib/aarch64-linux-musl/musl-gcc.specs
- 动态链接器:-dynamic-linker /lib/ld-musl-aarch64.so.1
- 传递 -nostdlib,表示不添加 glibc 标准库的搜索路径,因为它会被 -L/usr/lib/aarch64-linux-musl 替代
- /usr/lib/aarch64-linux-musl/ 下的 Scrt1.o,crti.o,crtbeginS.o,crtn.o:musl C 库提供的 C Runtime routine。
- -L/usr/lib/aarch64-linux-musl -L /usr/lib/gcc/aarch64-linux-gnu/13/:musl C 库和 gcc 库搜索路径
- /usr/lib/gcc/aarch64-linux-gnu/13/ 下的 libgcc.a,libgcc_eh.a,crtendS.o:gcc 提供的异常处理和 C runtime routine。
- -lc:实际使用的是 -L/usr/lib/aarch64-linux-musl 下的 musl c 库。
Ubuntu aarch64 官方:
- 只提供了原生架构的 musl 编译器工具链包:
musl, musl-dev, musl-tools
,没有提供 x86_64 架构的交叉编译 musl 库。 - 只提供了 musl-gcc wrapper,而没有提供 musl-clang wrapper 脚本。
解决方案:从 musl.cc 或 more.musl.cc 社区项目下载其它架构的 musl 库,或者从 musl 源码(见后文)来构建生成这两个 wrapper 脚本。
musl-gcc:交叉编译工具链 #
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 的二进制。
以 musl.cc 项目提供的 x86_64-linux-musl-cross 为例,安装过程如下:
# 创建工具链目录
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 架构的
# 如果要在 Ubuntu aarch64 系统上运行,则必须开启的 Ubuntu 的多架构支持。
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
将下载的 x86_64-linux-musl-cross 安装到 /usr/local/musl 目录下后,/usr/local/musl/x86_64-linux-musl-cross 即为 musl 的 sysroot 目录:
root@ubuntu:/Users/alizj/code/rust/my-demo# ls /usr/local/musl/x86_64-linux-musl-cross
bin include lib libexec share usr x86_64-linux-musl
root@ubuntu:/Users/alizj/code/rust/my-demo# ls /usr/local/musl/x86_64-linux-musl-cross/x86_64-linux-musl/lib/libc*
/usr/local/musl/x86_64-linux-musl-cross/x86_64-linux-musl/lib/libc.a /usr/local/musl/x86_64-linux-musl-cross/x86_64-linux-musl/lib/libc.so
/usr/local/musl/x86_64-linux-musl-cross/x86_64-linux-musl/lib/libcrypt.a
这两个项目提供的 *-musl-gcc,如 /usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc 是实际 ELF 可执行程序,而非 gcc 的 wrapper 脚本:
root@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
root@ubuntu:~# echo 'int main(void){}' >main.c
root@ubuntu:~# /usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc -g -v -Wp,-v -Wa,-v -Wl,-v main.c
#...
Configured with: ../src_gcc/configure --enable-languages=c,c++,fortran CC='gcc -static --static' CXX='g++ -static --static' FC='gfortran -static --static' CFLAGS='-g0 -O2 -fno-align-functions -fno-align-jumps -fno-align-loops -fno-align-labels -Wno-error' CXXFLAGS='-g0 -O2 -fno-align-functions -fno-align-jumps -fno-align-loops -fno-align-labels -Wno-error' FFLAGS='-g0 -O2 -fno-align-functions -fno-align-jumps -fno-align-loops -fno-align-labels -Wno-error' LDFLAGS='-s -static --static' --enable-default-pie --enable-static-pie --disable-cet --disable-bootstrap --disable-assembly --disable-werror --target=x86_64-linux-musl --prefix= --libdir=/lib --disable-multilib --with-sysroot=/x86_64-linux-musl --enable-tls --disable-libmudflap --disable-libsanitizer --disable-gnu-indirect-function --disable-libmpx --enable-initfini-array --enable-libstdcxx-time=rt --enable-deterministic-archives --enable-libstdcxx-time --enable-libquadmath --enable-libquadmath-support --disable-decimal-float --with-build-sysroot=/tmp/m1132/build/local/x86_64-linux-musl/obj_sysroot AR_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/ar AS_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/gas/as-new LD_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/ld/ld-new NM_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/nm-new OBJCOPY_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/objcopy OBJDUMP_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/objdump RANLIB_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/ranlib READELF_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/readelf STRIP_FOR_TARGET=/tmp/m1132/build/local/x86_64-linux-musl/obj_binutils/binutils/strip-new --build=x86_64-pc-linux-muslx32 --host=x86_64-pc-linux-muslx32
#...
/usr/local/musl/x86_64-linux-musl-cross/bin/../libexec/gcc/x86_64-linux-musl/11.2.1/cc1 -quiet -v -iprefix /usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/ -isysroot /usr/local/musl/x86_64-linux-musl-cross/bin/../x86_64-linux-musl -v main.c -quiet -dumpdir a- -dumpbase main.c -dumpbase-ext .c -mtune=generic -march=x86-64 -g -version -o /tmp/cccgIajA.s
#...
#include <...> search starts here:
/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/include
/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/include
#...
/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/bin/as -v --gdwarf-5 --64 -v -o /tmp/ccOHaOgo.o /tmp/cccgIajA.s
#...
/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/bin/ld --sysroot=/usr/local/musl/x86_64-linux-musl-cross/bin/../x86_64-linux-musl --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib/ld-musl-x86_64.so.1 -pie /usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/lib/Scrt1.o /usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/lib/crti.o /usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/crtbeginS.o -L/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1 -L/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc -L/usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/lib -L/usr/local/musl/x86_64-linux-musl-cross/bin/../x86_64-linux-musl/lib -v /tmp/ccOHaOgo.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/crtendS.o /usr/local/musl/x86_64-linux-musl-cross/bin/../lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/lib/crtn.o
使用 musl-cross-make 项目手动编译出交叉编译工具链
musl.cc 项目的局限是它们都只提供了 Host 架构是 amd64 的二进制交叉编译工具链,只能在 x86 或 x86_64 架构的机器上运行,而不能 aarch64 机器上运行。
对于 Host 架构是 aarch64 的编译环境,可以使用 musl-cross-make 或 musl-cross 或 crosstool-ng 项目,它们都是从源码编译出可以在编译环境的机器上运行的交叉编译工具链。
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
root@ubuntu:/home/alizj/musl-cross-make# file output/bin/x86_64-linux-musl-gcc
output/bin/x86_64-linux-musl-gcc: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=a76254d78dcf8fb96a4fb61fb53abf44e7b766b2, for GNU/Linux 3.7.0, with debug_info, not stripped
后续可以使用 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
MacOS 使用的交叉编译工具链项目 messense/homebrew-macos-cross-toolchains 也是使用 crosstool-ng 项目来生成的。
使用 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
MacOS 和 musl 交叉编译 #
brew tap messense/macos-cross-toolchains # 实际使用 crosstool-NG 生成的工具链
brew install x86_64-unknown-linux-musl
brew install aarch64-unknown-linux-musl
手动编译 musl #
原生架构编译:忽略。
交叉架构编译:需要系统先安装对应架构的编译工具链(gcc、libc、libgcc 等),然后使用对应架构的 gcc 或 clang 进行交叉编译。
交叉编译参数:
-
同时指定 –target 和 –build,–target != –build 时判断为交叉编译;
-
内部使用交叉编译器 {–target}-{gcc|c99|cc|} 来编译 musl 源码,这样生成目标架构的 musl C 库。
-
指定 –enable-wrapper=all 后,会生成 musl-clang、musl-gcc 脚本,内部封装了对 clang、{–target}-{gcc|c99|cc|} 的调用,以及链接生成的 musl 库和头文件。
-
交叉编译:使用 gcc
- 先安装 gcc 交叉编译工具链,提供对应的 libc、libgcc 库
root@ubuntu:~/musl-1.2.5# ./configure --prefix=/usr/local/musl/x86_64-linux-gnu --target=x86_64-linux-gnu --build=aarch64-linux-gnu --enable-wrapper=all
root@ubuntu:~/musl-1.2.5# ls obj/
crt include ld.musl-clang ldso musl-clang musl-gcc src
# musl-clang 内部调用 --target 指定的
root@ubuntu:~/musl-1.2.5# head obj/musl-clang
#!/bin/sh
cc="clang"
libc="/usr/local/musl/x86_64-linux-gnu"
libc_inc="/usr/local/musl/x86_64-linux-gnu/include"
libc_lib="/usr/local/musl/x86_64-linux-gnu/lib"
thisdir="`cd "$(dirname "$0")"; pwd`"
root@ubuntu:~/musl-1.2.5# ls lib/
crt1.o crti.o crtn.o libc.a libcrypt.a libc.so libdl.a libm.a libpthread.a libresolv.a librt.a libutil.a libxnet.a musl-gcc.specs rcrt1.o Scrt1.o
root@ubuntu:~/musl-1.2.5# file lib/libc.so
lib/libc.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=b7ea13ab4925034efb932466b4d84a9cb3d01553, not stripped
- 交叉编译:使用 clang
- 先安装 gcc 交叉编译工具链,提供对应的 libc、libgcc 库;
- 为 clang 指定 –target=x86_64-linux-gnu 参数;
# OK: 同时指定 --target、--build、CC 和 CFLAGS
root@ubuntu:~/musl-1.2.5# ./configure --prefix=/usr/local/musl/x86_64-linux-gnu --target=x86_64-linux-gnu --build=aarch64-linux-gnu --enable-wrapper=all CC=clang CFLAGS=--target=x86_64-linux-gnu
# OK:在指定 CROSS_COMPILE 的情况下,只需要指定 --target,而不需要指定 --build
root@ubuntu:~/musl-1.2.5# ./configure --prefix=/usr/local/musl/x86_64-linux-gnu --target=x86_64-linux-gnu --enable-wrapper=all CC=clang CFLAGS=--target=x86_64-linux-gnu CROSS_COMPILE=x86_64-linux-gnu-
# 错误:没有为 clang 指定 --target 参数
root@ubuntu:~/musl-1.2.5# ./configure --prefix=/usr/local/musl/x86_64-linux-gnu --target=x86_64-linux-gnu --build=aarch64-linux-gnu --enable-wrapper=all CC=clang
root@ubuntu:~/musl-1.2.5# make
#...
<inline asm>:10:2: error: unrecognized instruction mnemonic
10 | call _start_c
| ^
8 errors generated.
make: *** [Makefile:150: obj/crt/Scrt1.o] Error 1
# 错误:没有指定 --target,报错和上面一致
root@ubuntu:~/musl-1.2.5# ./configure --prefix=/usr/local/musl/x86_64-linux-gnu --enable-wrapper=all CC=clang CFLAGS=--target=x86_64-linux-gnu
# 错误:没有指定 --target,报错和上面一致
root@ubuntu:~/musl-1.2.5# ./configure --prefix=/usr/local/musl/x86_64-linux-gnu --enable-wrapper=all CC=clang CFLAGS=--target=x86_64-linux-gnu CROSS_COMPILE=x86_64-linux-gnu-
clang 工具链 #
clang 交叉编译参考:https://clang.llvm.org/docs/CrossCompilation.html
llvm 的编译器 clang 和链接器 lld 天然支持多架构,所以不需要安装多套。
clang 使用 --target <target-triplet>
参数来指定目标架构类型:如果是原生 Host 架构则可选,但如果是交叉编译则必须指定。
<target-triplet>
的格式和 Rust Target Triple 一致,都是: <arch><sub>-<vendor>-<sys>-<env|api>
示例:
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
# 查看 clang 的默认目标( Host 架构)
root@ubuntu:/Users/alizj/code/rust/my-demo# clang -dumpmachine
aarch64-unknown-linux-gnu
# 查看 clang 支持的 target 列表
root@ubuntu:/Users/alizj/code/rust/my-demo# clang -print-targets -v
Ubuntu clang version 18.1.3 (1ubuntu1)
Target: aarch64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/aarch64-linux-gnu/13
Selected GCC installation: /usr/bin/../lib/gcc/aarch64-linux-gnu/13
Candidate multilib: .;@m64
Selected multilib: .;@m64
Registered Targets:
aarch64 - AArch64 (little endian)
aarch64_32 - AArch64 (little endian ILP32)
aarch64_be - AArch64 (big endian)
amdgcn - AMD GCN GPUs
arm - ARM
arm64 - ARM64 (little endian)
arm64_32 - ARM64 (little endian ILP32)
armeb - ARM (big endian)
avr - Atmel AVR Microcontroller
bpf - BPF (host endian)
bpfeb - BPF (big endian)
bpfel - BPF (little endian)
#...
r600 - AMD GPUs HD2XXX-HD6XXX
riscv32 - 32-bit RISC-V
riscv64 - 64-bit RISC-V
#...
wasm32 - WebAssembly 32-bit
wasm64 - WebAssembly 64-bit
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
xcore - XCore
xtensa - Xtensa 32
clang 交叉编译 #
使用 clang 进行交叉编译前需要:
- 先安装对应架构的 gcc 或 musl 交叉编译工具链;
- 通过
--target
参数来指定目标架构,clang 会自动选择对应的交叉编译工具链目录; - 通过
--fuse-ld
来自动目标架构连接器或支持多架构的 lld 链接器;
gcc 和 clang 默认都使用原生架构的 ld 链接器,则在交叉编译时会报错:
root@ubuntu:~# echo 'int main(void){}' >main.c
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl -v main.c
#...
"/usr/bin/ld" -z relro --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -pie -dynamic-linker /lib/ld-musl-x86_64.so.1 -o a.out /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/Scrt1.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/crti.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/crtbeginS.o -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib64 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../lib64 -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib64 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib -L/lib -L/usr/lib /tmp/main-80b23e.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/crtendS.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/crtn.o
/usr/bin/ld: unrecognised emulation mode: elf_x86_64
Supported emulations: aarch64linux aarch64elf aarch64elf32 aarch64elf32b aarch64elfb armelf armelfb aarch64linuxb aarch64linux32 aarch64linux32b armelfb_linux_eabi armelf_linux_eabi
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解决办法:
- 使用 -fuse-ld=/usr/bin/x86_64-linux-gnu-ld 来指定目标架构的链接器,这里必须指定绝对路径,否则会报错:
root@ubuntu:~# echo 'int main(void){}' >main.c
# ERROR:
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl -v -fuse-ld=x86_64-linux-gnu-ld main.c
#...
clang: error: invalid linker name in argument '-fuse-ld=x86_64-linux-gnu-ld'
# OK:
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl -v -fuse-ld=/usr/bin/x86_64-linux-gnu-ld main.c
#...
"/usr/lib/llvm-18/bin/clang" -cc1 -triple x86_64-unknown-linux-musl -emit-obj -mrelax-all -dumpdir a- -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name main.c -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root -v -fcoverage-compilation-dir=/root -resource-dir /usr/lib/llvm-18/lib/clang/18 -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/main-63c0a8.o -x c main.c
clang -cc1 version 18.1.3 based upon LLVM 18.1.3 default target aarch64-unknown-linux-gnu
#...
"/usr/bin/x86_64-linux-gnu-ld" -z relro --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -pie -dynamic-linker /lib/ld-musl-x86_64.so.1 -o a.out /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/Scrt1.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/crti.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/crtbeginS.o -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib64 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../lib64 -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib64 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib -L/lib -L/usr/lib /tmp/main-63c0a8.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/crtendS.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/crtn.o
- 使用支持多架构的链接器 lld, lld 支持的参数和 ld 是兼容的。
root@ubuntu:~# echo 'int main(void){}' >main.c
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl -v -fuse-ld=lld main.c
#...
"/usr/lib/llvm-18/bin/clang" -cc1 -triple x86_64-unknown-linux-musl -emit-obj -mrelax-all -dumpdir a- -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name main.c -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/root -v -fcoverage-compilation-dir=/root -resource-dir /usr/lib/llvm-18/lib/clang/18 -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/main-b3f449.o -x c main.c
clang -cc1 version 18.1.3 based upon LLVM 18.1.3 default target aarch64-unknown-linux-gnu
#...
"/usr/bin/ld.lld" -z relro --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -pie -dynamic-linker /lib/ld-musl-x86_64.so.1 -o a.out /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/Scrt1.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/crti.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/crtbeginS.o -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib64 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../lib64 -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib64 -L/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib -L/lib -L/usr/lib /tmp/main-b3f449.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/crtendS.o /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/crtn.o
第二个问题:clang 根据 --target
参数选择交叉编译工具链库目录可能是错误的(特别是 *-linux-musl target
的情况):
- –target=x86_64-unknown-linux-gnu: 正确发现和选择 /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
- –target=aarch64-linux-gnu: 正确发现和选择 /usr/bin/../lib/gcc/aarch64-linux-gnu/13
- –target=x86_64-unknown-linux-musl: 错误的选择 /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
# 查看支持的某个 target 详情:
## 1. 原生架构:aarch64-linux-gnu,使用的 gcc 为 /usr/lib/gcc/aarch64-linux-gnu,根据 -target 自动搜索对应 gcc 和 libc 头文件目录:
root@ubuntu:~# clang -v -xc /dev/null -fsyntax-only
#...
Found candidate GCC installation: /usr/bin/../lib/gcc/aarch64-linux-gnu/13
Selected GCC installation: /usr/bin/../lib/gcc/aarch64-linux-gnu/13
#...
## 2. 交叉编译架构:x86_64-linux-gnu,使用的 gcc 为 /usr/lib/gcc-cross/x86_64-linux-gnu,根据 -target 自动搜索对应 gcc 和 libc 头文件目录
root@ubuntu:~# clang --target=x86_64-unknown-linux-gnu -v -xc /dev/null -fsyntax-only
#...
Found candidate GCC installation: /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
Selected GCC installation: /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
#...
## 3. 交叉编译架构:x86_64-linux-musl,错误的使用 /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl -v -xc /dev/null -fsyntax-only
#...
Found candidate GCC installation: /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
Selected GCC installation: /usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13
#...
解决办法:
- 使用
--sysroot=/usr/local/musl/x86_64-linux-musl-cross
指定 musl 安装目录:
- –sysroot=
:指定解压的交叉编译工具链目录,主要是是 glibc 的头文件或库文件。 - -I 和 -L: 指定搜索、链接的头文件、库文件
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl --sysroot=/usr/local/musl/x86_64-linux-musl-cross -v -xc /dev/null -fsyntax-only
#...
Found candidate GCC installation: /usr/local/musl/x86_64-linux-musl-cross/lib/gcc/x86_64-linux-musl/11.2.1
Selected GCC installation: /usr/local/musl/x86_64-linux-musl-cross/lib/gcc/x86_64-linux-musl/11.2.1
#...
- 使用 musl-clang wrapper for clang 脚本(安装的 musl 工具链提供,或者手动编译 musl 源码时安装),它内部自动查找和链接 musl 库和头文件。
root@ubuntu:~# musl-clang --target=x86_64-unknown-linux-musl # 不需要指定 --sysroot 目录
查看 clang 链接时搜索的库文件目录:
# 原生架构: /usr/lib/gcc/aarch64-linux-gnu/13, /usr/lib/aarch64-linux-gnu
root@ubuntu:~# clang -print-search-dirs
programs: =/usr/bin:/usr/lib/llvm-18/bin:/usr/bin/../lib/gcc/aarch64-linux-gnu/13/../../../../aarch64-linux-gnu/bin
libraries: =/usr/lib/llvm-18/lib/clang/18:/usr/bin/../lib/gcc/aarch64-linux-gnu/13:/usr/bin/../lib/gcc/aarch64-linux-gnu/13/../../../../lib64:/lib/aarch64-linux-gnu:/lib/../lib64:/usr/lib/aarch64-linux-gnu:/usr/lib/../lib64:/lib:/usr/lib
# 交叉编译架构:/usr/lib/gcc-cross/x86_64-linux-gnu/13, /usr/lib/x86_64-linux-gnu
root@ubuntu:~# clang -target x86_64-unknown-linux-gnu -print-search-dirs
programs: =/usr/bin:/usr/lib/llvm-18/bin:/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/bin
libraries: =/usr/lib/llvm-18/lib/clang/18:/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13:/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib/../lib64:/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../lib64:/lib/x86_64-linux-gnu:/lib/../lib64:/usr/lib/x86_64-linux-gnu:/usr/lib/../lib64:/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/lib:/lib:/usr/lib
# 交叉编译架构:x86_64-unknown-linux-musl
root@ubuntu:~# clang --target=x86_64-unknown-linux-musl --sysroot=/usr/local/musl/x86_64-linux-musl-cross -print-search-dirs
programs: =/usr/bin:/usr/lib/llvm-18/bin:/usr/local/musl/x86_64-linux-musl-cross/lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/bin
libraries: =/usr/lib/llvm-18/lib/clang/18:/usr/local/musl/x86_64-linux-musl-cross/lib/gcc/x86_64-linux-musl/11.2.1:/usr/local/musl/x86_64-linux-musl-cross/lib/gcc/x86_64-linux-musl/11.2.1/../../../../x86_64-linux-musl/lib:/usr/local/musl/x86_64-linux-musl-cross/lib:/usr/local/musl/x86_64-linux-musl-cross/usr/lib
Rust Target Triple #
https://doc.rust-lang.org/cargo/appendix/glossary.html#target
Target Triple :用来指定特定架构、环境或 ABI 的字符串。
后续编译时,可以在 cargo build --target=<target-triplet>
或 .cargo/config.toml
的 build.target
中指定 triplet 字符串。
triplet 格式:<arch><sub>-<vendor>-<sys>-<abi>
arch
:CPU 架构,如x86_64, i686, arm, thumb, mips
等;sub
:CPU 子架构,如 arm 包含v7, v7s, v5te
等;vendor
: 机器硬件制造商,如unknown, apple, pc, nvidia
,- Linux 系统一般为
unknown
- MacOS 为
apple
- Windows 为
pc
- Linux 系统一般为
sys
: 操作系统 OS 名称,如linux, windows, darwin,none
none
:一般用于不使用 OS 的 bare-metal ELF 二进制。
abi
: ABI 或系统环境,如gnu, android, eabi,musl,elf
eabi
:嵌入式 ABI,一般用于 arm CPUgnu
:表示使用 glibc 库musl
:表示使用 musl libc 库elf
:一般用于 bare-metal 的嵌入式 bin 固件程序
说明:Rust target 和 clang 的 –target 值语法格式是一致的。
查看 rustc 支持的 target 列表:
$ rustup target list |grep installed
# 或者
$ rustc --print=target-list
Rust 官方对各 taget 是有不同稳定性和支持级别的,Tier1 级别的 target 如下:
aarch64-unknown-linux-gnu
ARM64 Linux (kernel 4.1, glibc 2.17+) 1i686-pc-windows-gnu
32-bit MinGW (Windows 7+) 2 3i686-pc-windows-msvc
32-bit MSVC (Windows 7+) 2 3i686-unknown-linux-gnu
32-bit Linux (kernel 3.2+, glibc 2.17+) 3x86_64-apple-darwin
64-bit macOS (10.12+, Sierra+)x86_64-pc-windows-gnu
64-bit MinGW (Windows 7+) 2x86_64-pc-windows-msvc
64-bit MSVC (Windows 7+) 2x86_64-unknown-linux-gnu
64-bit Linux (kernel 3.2+, glibc 2.17+)
rustup 安装和管理 target #
cargo build
默认编译生成 host arch
二进制。
使用 rustup show
命令查看 host arch
类型,安装的 toolchain、当前缺省 toolchain 以及安装的 target:
root@ubuntu:/Users/alizj/code/rust/my-demo# rustup show
Default host: aarch64-unknown-linux-gnu
rustup home: /root/.rustup
installed toolchains
--------------------
stable-aarch64-unknown-linux-gnu (active, default)
nightly-aarch64-unknown-linux-gnu
active toolchain
----------------
name: stable-aarch64-unknown-linux-gnu
active because: it's the default toolchain
installed targets:
aarch64-unknown-linux-gnu
aarch64-unknown-linux-musl
x86_64-unknown-linux-gnu
x86_64-unknown-linux-musl
查看 rustc 编译器支持的 target 列表:
zj@a:~/docs$ rustc --print=target-list
aarch64-apple-darwin
aarch64-apple-ios
aarch64-apple-ios-macabi
aarch64-apple-ios-sim
aarch64-apple-tvos
aarch64-apple-tvos-sim
aarch64-apple-watchos
#...
x86_64-unknown-redox
x86_64-unknown-uefi
x86_64-uwp-windows-gnu
x86_64-uwp-windows-msvc
x86_64-win7-windows-msvc
x86_64-wrs-vxworks
x86_64h-apple-darwin
打印已安装的 target:
*-linux-gnu
:生成的二进制动态链接到 libgcc_s.so 和 glibc;*-linux-musl
:生成的二进制静态链接到 Rust 标准库(该 target 的 Rust 标准库已经是链接 musl 的实现,不依赖于 libc 和 libgcc),生成静态链接的二进制;
root@ubuntu:~# rustup target list |grep installed
aarch64-unknown-linux-gnu (installed)
aarch64-unknown-linux-musl (installed)
x86_64-unknown-linux-gnu (installed)
x86_64-unknown-linux-musl (installed)
rust toolchian 默认只安装了 host arch
target,其它 target 需要单独安装。
安装 target 的过程实际是安装对应架构 Rust 标准库的过程。
rustup target add x86_64-unknown-linux-gnu
# 安装的对应架构标准库
❯ ls ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-6f060101dda10b7a.*
/Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-6f060101dda10b7a.rlib
/Users/alizj/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-6f060101dda10b7a.so*
查看 target 的 spec 配置参数,如 linker-flavor:
- aarch64-unknown-linux-gnu|musl, x86_64-unknown-linux-musl
- “linker-flavor”: “gnu-cc”:使用 cc 编译器和链接器,链接到 glibc;
- x86_64-unknown-linux-gnu
- “linker-flavor”: “gnu-lld-cc”:使用 cc 编译器和 lld 链接器,链接到 glibc;
root@ubuntu:/Users/alizj/code/rust/my-demo# rustc +nightly -Z unstable-options --target=aarch64-unknown-linux-gnu --print target-spec-json |grep fla
"linker-flavor": "gnu-cc",
Rust *-linux-musl target #
执行命令 rustup target add x86_64-unknown-linux-musl
添加 *-linux-musl
target 时,rustup 会下载并安装针对该目标的 Rust 标准库(libstd),而且这个标准库本身就是静态链接的 musl 版本。
- 但不会下载和安装 musl 库本身,也不会提供 musl-gcc、musl-ld 等工具链依赖。
Rust 项目官方对 *-unknown-linux-musl 目标提供了完整的 预构建标准库,它:
- 使用 musl 工具链构建
- 链接了 musl 所需的 crt startup objects(如 crt1.o, crti.o, crtn.o)
- 默认配置 rustc 使用内部的 musl-gcc 或 clang + musl 的 linker 参数(依赖于系统)
当后续编译时,只要指定 --target=xxx-musl
参数,Rust 会自动选择这些静态库来链接你的程序,无需单独安装 musl 库。
- rustc 自动完成了静态链接
- musl 库已经包含在官方编译的 libstd 静态库中
对于大多数纯 Rust 代码,只依赖 Rust 标准库,不依赖 C 语言的第三方库(如 OpenSSL、libz 等),Rust 的 musl 目标就能完全做到静态链接, 无需额外的 musl 系统库或工具链, 因为所有需要的东西都已经在 rustup 下载的目标包里了。
但是如下两种情况,则还需要单独安装 musl 包:
- 如果项目还需要链接 C 语言的第三方依赖(比如 FFI、C 库),那就有可能需要系统上有 musl 的头文件和静态库用于链接。
- 交叉编译场景,必须安装目标架构的 musl 包。
Rust 程序动态链接 libc 和 libgcc 库 #
即使对于纯 Rust 代码项目(项目中没有使用 FFI 链接 C/C++ 代码),在生成 *-linux-gnu target
的二进制,默认会动态链接到 gcc 的 libgcc_s.so 库和 glibc 库:
root@ubuntu:/Users/alizj/code/rust/my-demo# uname -a
Linux ubuntu 6.13.7-orbstack-00283-g9d1400e7e9c6 #104 SMP Mon Mar 17 06:15:48 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux
root@ubuntu:/Users/alizj/code/rust/my-demo# cargo build
root@ubuntu:/Users/alizj/code/rust/my-demo# ldd target/debug/my-demo
linux-vdso.so.1 (0x0000ffffa1dab000)
libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ffffa1cb0000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffa1af0000)
/lib/ld-linux-aarch64.so.1 (0x0000ffffa1d60000)
实现静态链接的方法:
- 使用 musl libc:
# 安装 musl 目标
rustup target add aarch64-unknown-linux-musl
# 使用 musl 编译
cargo build --target aarch64-unknown-linux-musl
如前面所述,rustup 安装的 *-linux-musl target 内部是使用静态链接到 musl Rust 标准库,也即这个类型的 Rust 标准库不依赖 libc 和 libgcc 动态库, 所以对于纯 Rust 项目,在编译原生架构二进制时,不需要额外安装 musl 库,可以生成纯静态的二进制。
- 或者使用 rustc 的
-C target-feature=+crt-static
实现静态链接,这时会静态链接 libgcc_s 库,Rust 标准库也切换到内部静态实现(不依赖 glibc):
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS='-C target-feature=+crt-static' cargo build
Compiling my-demo v0.1.0 (/Users/alizj/code/rust/my-demo)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
root@ubuntu:/Users/alizj/code/rust/my-demo# ldd target/debug/my-demo
not a dynamic executable
rustc 交叉编译 #
rustc 编译器基于 LLVM 实现,支持交叉编译,但需要:
- 安装对应架构的 target,它提供了对应架构的 Rust 标准库;
- 安装对应架构的交叉编译工具链,它提供了对应架构的编译器、链接器、libc 库、头文件、库文件等;
打印 rustc 支持并已安装的 target:
root@ubuntu:~# rustup target list |grep installed
aarch64-unknown-linux-gnu (installed)
aarch64-unknown-linux-musl (installed)
x86_64-unknown-linux-gnu (installed)
x86_64-unknown-linux-musl (installed)
# taget 安装到了 ~/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/,安装的是对应架构的 rust std 库。
root@ubuntu:~# ls ~/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/
由于 Rust 程序默认动态链接到 glibc 和 gcc 库,所以 clang 交叉编译类似,Rust 程序的交叉编译前也需要安装对应架构的交叉编译工具链。
cargo build 时,通过 –target 参数指定要构建的目标架构 triplet target,根据该 target 下配置(.cargo/config.toml
文件或 cargo build 命令行参数)的 linker、linker-flavor、link-arg 等参数,调用对应交叉编译器(linker)并给它传递参数(link-arg),由该交叉编译器为 Rust 程序正确链接交叉编译工具链提供的 libc 库、gcc 库(musl 也包含 libgcc_s.so 等库)。
示例:MacOS aarch64 交叉编译 amd64 和 aarch64 linux 程序
- 安装 gcc 交叉编译工具链,后续 rustc 交叉编译时需要使用其中的编译器
brew tap messense/macos-cross-toolchains
brew install x86_64-unknown-linux-musl
brew install aarch64-unknown-linux-musl
- 安装 target 对应的 Rust 标准库
# 动态链接,默认已安装
rustup target add aarch64-unknown-linux-gnu
rustup target add x86_64-unknown-linux-gnu
# 静态链接,使用 musl 目标
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-musl
-
配置项目
.cargo/config.toml
文件,指定 linker 和 rustflags 参数:
# https://github.com/zed-industries/zed/blob/main/.cargo/config.toml
#
# 缺省构建的 target 类型(一般是 host arch)和编译器参数
[build]
# rustc 目标架构字符串或列表,未指定时为当前系统架构,
# 如果 cargo build 没有指定 --target,默认自动构建这里指定的一个或多个 target
target = "x86_64-unknown-linux-gnu"
# 指定 linker driver 二进制(可以为绝对路径,不能指定链接器,如 ld、ldd,因为它们不能找到正确的 libc、libgcc 库)。
# 默认是 cc,对应的是 Host 架构的 gcc 或 clang。
# 如果使用 gcc 编译器,在交叉编译时,需要使用带架构前缀的 gcc,如 x86_64-unknown-gnu-gcc,xx-musl-gcc 等。
# 推荐使用 clang,它支持多架构。
linker = "clang"
# rustflags 指定传递给 rustc 的命令行参数
# link-arg 为链接器传递参数,等效为: rustc -C link-arg=-fuse-ld=mold
# rustc 调用 clang linker,所以进一步等效为:clang -fuse-ld=mold
# 具体参考:https://doc.rust-lang.org/rustc/codegen-options/index.html
#
# 对于 clang 交叉编译,如果是交叉编译,则需要指定 --target,同时指定使用 lld 链接器(默认是 ld,速度较慢)
rustflags = ["-C", "link-arg=--target=x86_64-unknown-linux-gnu", "-C", "link-arg=-fuse-ld=lld"]
# 当编译的 rustc target 为 x86_64-unknown-linux-gnu 时生效
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
# 按照 target 类型配置链接器和编译器参数
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["…", "…"]
- 交叉编译:
使用 --target
指定目标架构,如果未指定则默认构建当前架构,或 .cargo/config.toml
的 [build].target
中指定的一个或多个架构二进制 :
cargo build --target aarch64-unknown-linux-gnu --release
cargo build --target x86_64-unknown-linux-gnu --release
# 或者静态链接
cargo build --target x86_64-unknown-linux-musl --release
cargo build --target aarch64-unknown-linux-musl --release
linker 和 linker-flavor 参数 #
rustc 支持两个 codegen 参数:-C linker=xx
和 -C linker-flavor=xx
:
-C linker
:指定用什么链接程序(一般是是 linker driver,如 gcc、cc、clang 等)-C linker-flavor
:指定应该给 linker 按哪种风格传递参数
如果指定了 linker,则 linker-flavor 自动从 linker 进行推导,例如:
- linker 是 cc、gcc、clang
- linker-flavor 都推导为 gcc,clang 兼容 gcc 风格的命令行参数(但也有差异,如支持 –target 参数)
- linker 是 ld.lld
- linker-flavor 推导是 ld.lld,即 rust 调用 lld 命令时传递
-flavor gnu
参数 - /usr/bin/ld.lld ->/usr/lib/llvm-18/bin/ld.lld ->/usr/lib/llvm-18/bin/lld
- linker-flavor 推导是 ld.lld,即 rust 调用 lld 命令时传递
- linker 是 ld64.lld
- linker-flavor 推导是 ld64.lld,即 rust 调用 lld 命令时传递
-flavor darwin
参数 - /usr/bin/ld64.lld-18 -> /usr/lib/llvm-18/bin/ld64.lld -> /usr/lib/llvm-18/bin/lld
- linker-flavor 推导是 ld64.lld,即 rust 调用 lld 命令时传递
- liner 是 ld-link
- linker-flavor 推导是 lld-link,即 rust 调用 lld 命令时传递
-flavor windows
参数
- linker-flavor 推导是 lld-link,即 rust 调用 lld 命令时传递
如果未指定 linker,则 linker-flavor 用于决定使用哪一个 linker。如果没有指定 linker-flavor,则自动从 target 推导,每一个 target 都有自己缺省的 linker 和 linker-flavor 类型:
- linux、bsd target:
- linker flavor 是 gcc,linker 是 cc。
- 对于 linux 有两个细分类型:
- linux-gnu:linker flavor 是 gcc,linker 是 cc(默认链接到 gcc)
- linux-musl:linker flavor 是 gcc,linker 是 musl-gcc
- macos、ios target:
- linker flavor 是 gcc,linker 是 cc(cc 链接到 clang)
- windows target:
- 默认使用 msvc flavor
rusct 支持的 stable linker-flavor
类型:https://doc.rust-lang.org/rustc/codegen-options/index.html#linker-flavor:
- em: use Emscripten emcc.
- gcc: use the
cc
executable, which is typicallygcc or clang
on many systems. - ld: use the
ld
executable. - msvc: use the
link.exe
executable from Microsoft Visual Studio MSVC. - wasm-ld: use the
wasm-ld
executable, a port of LLVM lld for WebAssembly. - ld64.lld: use the LLVM lld executable with the
-flavor darwin
flag for Apple’s ld. - ld.lld: use the LLVM lld executable with the
-flavor gnu
flag for GNU binutils’ ld. - lld-link: use the LLVM lld executable with the
-flavor link
flag for Microsoft’s link.exe.
rustc 还支持一些 unstable linker-flavor
类型,后续会逐渐取代上面的部分 stable 类型:https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/codegen-options.html
gnu
: unix-like linker with GNU extensions -> 直接调用 GNU ld 链接器。一般不使用!gnu-lld
: gnu using LLD -> 调用链接器 lld -flavor gnu 链接器。一般不使用。gnu-cc
: gnu using a C/C++ compiler as the linker driver -> 调用 cc,默认gnu-lld-cc
: gnu using LLD and a C/C++ compiler as the linker driver -> 调用 cc 并传入 lld 作为链接器,等效为cc -fuse-ld=ld.lld
-> x86_64-unknown-linux-gnu 将默认使用该类型。- darwin: unix-like linker for Apple targets
- darwin-lld: darwin using LLD
- darwin-cc: darwin using a C/C++ compiler as the linker driver
- darwin-lld-cc: darwin using LLD and a C/C++ compiler as the linker driver
- wasm-lld: unix-like linker for Wasm targets, with LLD
- wasm-lld-cc: unix-like linker for Wasm targets, with LLD and a C/C++ compiler as the linker driver
- unix: basic unix-like linker for “any other Unix” targets (Solaris/illumos, L4Re, MSP430, etc), not supported with LLD.
- unix-cc: unix using a C/C++ compiler as the linker driver
- msvc-lld: MSVC-style linker for Windows and UEFI, with LLD
- em-cc: emscripten compiler frontend, similar to wasm-lld-cc with a different interface
需要在指定 -Z unstable-options 的前提下来使用上面 unstable 的 linker-flavor 类型值。
查看 target 使用的 linker-flavor 类型: rustc +nightly -Z unstable-options --print target-spec-json --target aarch64-unknown-linux-gnu
:
- x86_64-unknown-linux-gnu target:
- “linker-flavor”: “gnu-lld-cc”
- 等效于 stable 的 ld.lld,但是优先使用 rustc 自带的 rust-lld,然后才是系统的 ld.lld(系统)。
- aarch64-unknown-linux-gnu target:
- “linker-flavor”:“gnu-cc”
- 等效于 stable 的 gcc
rustc 支持使用 linker-flavor 和 link-self-contained=+linker 参数来使用的链接器类型:
- Self-contained:-Z unstable-options -C linker-flavor=gnu-lld-cc -C link-self-contained=+linker
- 使用 rustc 自带的 rust-lld 链接器
- External:-Z unstable-options -C linker-flavor=gnu-lld-cc
- 使用系统的 lld 链接器
link-self-contained 说明:https://github.com/ph-One/rust/blob/master/src/doc/unstable-book/src/compiler-flags/codegen-options.md?plain=1#L38C4-L38C23
linker 值需要设置为编译器而非链接器 #
对于 linker 一般指定为 gcc、clang 等编译器(linker driver),而不是直接指定为 ld、lld 等链接器。
这是因为 lld 等是一个 linker backend,用于处理目标文件(.o、.a)的链接,它不会自动添加 startup objects、库搜索路径、默认链接参数等。
编译器知道如何链接 libc、libgcc 库,而 ld、lld 不知晓。
如果将 linker 指定为 lld,编译时会报错:
- ld.lld: error: undefined reference to
_start
- ld.lld: error: cannot find crt1.o: No such file or directory
RUSTFLAGS="-C linker=lld" cargo build
ld.lld: error: undefined reference to `_start`
ld.lld: error: unable to find library -lc
ld.lld: error: unable to find crt1.o
正确做法:linker 指定为编译器,然后通过 -fuse-ld 连指定链接器,编译器在调用链接器时传正确的 startup objects 的所有路径和链接要求:
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-args=-fuse-ld=lld"]
linker 指定为 ld、lld 等链接器的场景:一般是 non_std bare-mental 应用,它们不需要链接 ligc、libgcc。《— 很少见
- 对于这类型二进制的编译,一般通过 build.rs 或配置置顶了自定义 linker script,且无 startup object files 依赖。
linker-flavor 的使用场景主要是指定 linker=lld 时使用,因为 ld64.lld、ld.lld、lld-link 都使用 lld 链接器,但是分别给 lld 指定不同的 -flavor 参数,用来指定 lld 链接到对应的 libc 库。
配置 linker、linker-flavor、link-args 参数 #
rustc 命令行配置参数:-C linker=xx,-C linker-flavor=xx,-C link-arg=xx,-C link-args="xx yy"
# 使用 gcc 和 lld(默认使用 ld)
rustc -C linker=gcc \
-C link-arg=-fuse-ld=lld \
main.rs
# 使用 clang 和 lld(默认使用 ld)
rustc -C linker=clang \
-C link-arg=-fuse-ld=lld \
main.rs
# macOS 上使用 LLD
# 使用 ld64.lld 替代系统 ld64
rustc --target x86_64-apple-darwin \
-C linker=ld64.lld \
-C linker-flavor=ld64.lld \
main.rs
cargo 配置方式:
- 在 .cargo/config.toml 中配置:
- 支持 linker,但不支持 linker-flavor、link-arg 字段;
- 可以在 rustflags 字段中通过 -C 来配置 linker-flavor、link-arg 值;
# .cargo/config.toml
[target.x86_64-unknown-linux-gnu]
# 不支持 linker-flavor、link-arg 字段
linker = "clang"
# 但是可以在 rustflags 中使用 -C 对 linker-flavor、link-arg 进行配置
rustflags = ["-C", "link-arg=--target=x86_64-unknown-linux-gnu", "link-arg=-fuse-ld=lld"]
- 在 cargo build 命令行上配置:
# 命令行指定:注意 rustflags 值为列表类型 [xxx]
cargo build --target x86_64-unknown-linux-musl \
--config "target.x86_64-unknown-linux-musl.linker='x86_64-linux-musl-gcc'" \
--config "target.x86_64-unknown-linux-musl.rustflags=['-C', 'linker-flavor=ld.lld']"
# 或者通过环境变量指定:
TRIPLET=x86_64-unknown-linux-gnu
RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc -C linker-flavor=ld.lld" cargo build --target $TRIPLET
rustc 编译实验 #
交叉编译参考:
- https://www.rectcircle.cn/posts/linux-dylib-detail-6-lang-rust/
- https://rust-lang.github.io/rustup/cross-compilation.html
使用 clang、gcc 作为 linker 时,如何支持交叉编译?
- 设置 linker 的值包含架构信息:
linker = "x86_64-linux-musl-gcc"
- 或者 linker 设置为支持多架构的 clang,但通过 rustflags 指定 –target 和 -fuse-ld=lld:
rustflags = ["-C", "link-arg=--target=x86_64-pc-windows-gnu", "link-arg=-fuse-ld=lld"]
- 因为 go、clang 默认使用的是原生架构的 ld 链接器。
编译 Host 架构二进制 #
OK: 编译生成 Host 架构二进制
- 直接 cargo build
- 或者指定 linker 为 cc、gcc、clang 或 aarch64-linux-gnu-gcc,它们都是 Host 架构的编译器
- 生成的二进制动态链接到 libgcc_s.so 和 glibc
root@ubuntu:/Users/alizj/code/rust/my-demo# cargo build -v
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" cargo build -v
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker=gcc" cargo build
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker=clang" cargo build
root@ubuntu:/Users/alizj/code/rust/my-demo# file target/debug/my-demo
target/debug/my-demo: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=096d62ba16e87322cdcffc6d7c50caa62eff3c51, for GNU/Linux 3.7.0, with debug_info, not stripped
root@ubuntu:/Users/alizj/code/rust/my-demo# ldd !$
ldd target/debug/my-demo
linux-vdso.so.1 (0x0000ffff98fcc000)
libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ffff98ee0000)
libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffff98d20000)
/lib/ld-linux-aarch64.so.1 (0x0000ffff98f90000)
OK: 指定 linker-flavor 为 gcc、gnu-cc、gnu-lld-cc
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker-flavor=gcc" cargo build -v
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-Z unstable-options -C linker-flavor=gnu-cc" cargo +nightly build -v
Compiling my-demo v0.1.0 (/Users/alizj/code/rust/my-demo)
Running `/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin/rustc --crate-name my_demo --edition=2021 src/main.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=191 --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 --check-cfg 'cfg(docsrs)' --check-cfg 'cfg(feature, values())' -C metadata=bbfe255f4584d4bf -C extra-filename=-bbfe255f4584d4bf --out-dir /Users/alizj/code/rust/my-demo/target/debug/deps -C incremental=/Users/alizj/code/rust/my-demo/target/debug/incremental -L dependency=/Users/alizj/code/rust/my-demo/target/debug/deps -C linker-flavor=gcc`
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
ERROR: 将 linker 指定为 ld, ld.lld 等链接器(而非编译器),或者将 linker-flavor 为 ld,lld,ld.lld,gnu, gnu-lld 等均失败,报错:
- ld: cannot find -lgcc_s: No such file or directory
- ld.lld: error: unable to find library -lgcc_s, -lutil, -lrt, -lpthread, -lm, -ldl, -lc
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker=ld" cargo build
Compiling my-demo v0.1.0 (/Users/alizj/code/rust/my-demo)
error: linking with `ld` failed: exit status: 1
= note: "ld" "/tmp/rustcyphlL0/symbols.o" "/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf.076z9konujxpqck8prt4iazcw.rcgu.o"
"/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf.ep50ackuubebsftth1zy6fz2v.rcgu.o" "--as-needed" "-Bstatic" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib/libstd-6031cf49ae85e068.rlib" "-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "--eh-frame-hdr" "-z" "noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf" "--gc-sections" "-pie" "-z" "relro" "-z" "now"
= note: ld: cannot find -lgcc_s: No such file or directory
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker-flavor=ld.lld" cargo build
"lld" "-flavor" "gnu" "/tmp/rustcoUgKqL/symbols.o"
"/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib/libcore-f75b3043b9a7467a.rlib" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib/libcompiler_builtins-d3c20515d689c7cc.rlib" "-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "--eh-frame-hdr" "-z" "noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf" "--gc-sections" "-pie" "-z" "relro" "-z" "now"
= note: lld: error: unable to find library -lgcc_s
lld: error: unable to find library -lutil
lld: error: unable to find library -lrt
lld: error: unable to find library -lpthread
lld: error: unable to find library -lm
lld: error: unable to find library -ldl
lld: error: unable to find library -lc
FAIL: 设置 linker=gcc,link-arg=-fuse-ld=ld.lld,这是因为 gcc 的 -fuse-ld 只支持固定的几个值:bfd、gold、lld、mold OK:RUSTFLAGS="-C linker=gcc -C link-arg=-fuse-ld=lld" cargo build -v OK: RUSTFLAGS="-C linker=gcc -C link-arg=-fuse-ld=mold" cargo build -v OK: RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=mold" cargo build -v
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker=gcc -C link-arg=-fuse-ld=ld.lld" cargo build
Compiling my-demo v0.1.0 (/Users/alizj/code/rust/my-demo)
error: linking with `gcc` failed: exit status: 1
|
= note:"gcc" "/tmp/rustcitDghM/symbols.o" "/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf.076z9konujxpqck8prt4iazcw.rcgu.o" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib/libcompiler_builtins-d3c20515d689c7cc.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-fuse-ld=ld.lld"
= note: gcc: error: unrecognized command-line option '-fuse-ld=ld.lld'; did you mean '-fuse-ld=lld'?
FAIL: linker 为 clang 时,不能使用 -fuse-ld=ld.lld,但可以使用 -fuse-ld=lld 或 -fuse-ld=/usr/bin/ld.lld, 也即 clang 的 -fuse-ld 值可以使用绝对路径,但是 gcc 不支持: OK: RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=/usr/bin/mold" cargo build -v FAIL: RUSTFLAGS="-C linker=gcc -C link-arg=-fuse-ld=/usr/bin/ld.lld" cargo build -v FAIL: RUSTFLAGS="-C linker=gcc -C link-arg=-fuse-ld=/usr/bin/mold" cargo build -v
root@ubuntu:/Users/alizj/code/rust/my-demo# RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=ld.lld" cargo build -v
error: linking with `clang` failed: exit status: 1
= note: "clang" "/tmp/rustcyjTneZ/symbols.o"
"/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib/libcore-f75b3043b9a7467a.rlib" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib/libcompiler_builtins-d3c20515d689c7cc.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/debug/deps/my_demo-bbfe255f4584d4bf" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-fuse-ld=ld.lld"
= note: clang: error: invalid linker name in argument '-fuse-ld=ld.lld'
总结:对于编译生成链接 gnu glibc 的二进制,一般会链接到对应架构的 libgcc_s 和 libc 库(比如 libgcc 库提供了 C 程序的 C runtime routine 库!),
- linker 参数应该指定的是编译器(linker driver)而非链接器,否则指定为链接器后会提示找不到 -lgcc_s。
- linker-flavor 参数应该指定为 gcc、gnu-cc、gnu-lld-cc,它们都是用对应的 cc 编译器作为 linker,不能使用 gnu、gnu-lld。
- linker 指定为 gcc、clang 时,可以使用 link-arg=-fuse-ld=XX 来指定使用的链接器
- gcc、clang 默认都使用 ld 作为连接器。
- 如果 linker=clang,则必须设置链接器为 lld 或对应架构的 ld。
- XX 只支持固定的几个值:bfd、gold、lld、mold,不包含 ld(因为它是 gcc、clang 默认的链接器)
- 对于 clang,XX 支持路径,如 /usr/bin/ld.lld
只有在不链接 libgcc_s 和 libc 库的嵌入式 bare-mental 场景,才有可能将 linker 直接设置为 ld、lld、mold 等。
交叉编译其它架构二进制 #
使用 clang 交叉编译 #
OK: 必须同时指定 linker=clang, link-arg=-fuse-ld=lld -Clink-arg=--target=$TRIPLET
才能交叉编译成功:
- 因为 clang、lld 都是支持多架构的,它们使用传递给 clang 的 –target 参数来构建和链接目标架构的二进制;
- 指定 –target 为交叉编译架构时,clang 会查找对应的交叉编译工具链目录(gcc、libc),链接对应的头文件和库文件。
- gcc、clang 默认使用原生架构链接器 ld,会导致交叉编译链接失败,所以必须指定链接器为 ldd 或对应架构的链接器 ld,如 -Clink-arg=-fuse-ld=/usr/bin/x86_64-linux-gnu-ld
# OK
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=lld -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
# OK:为 clang 指定目标架构使用的 x86_64-linux-gnu-ld
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=/usr/bin/x86_64-linux-gnu-ld -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
# FAILED:未指定 -Clink-arg=-fuse-ld 时 clang 默认使用 ld,报错:
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
error: linking with `clang` failed: exit status: 1
= note: LC_ALL="C" PATH="/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/bin:/root/.cargo/bin:/root/go/bin:/usr/lib/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin" VSLANG="1033" "clang" "-m64"
/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-3d4809363f767eb8.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/x86_64-unknown-linux-gnu/debug/deps/my_demo-1bb942ce8cca92ee" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "--target=x86_64-unknown-linux-gnu"
= note: clang: error: no such file or directory: '/Users/alizj/code/rust/my-demo/target/x86_64-unknown-linux-gnu/debug/deps/my_demo-1bb942ce8cca92ee.9kr8zfbiercws8stzlb40jdab.rcgu.o'
# 失败
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=lld" cargo build --target $TRIPLET
# 失败
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang" cargo build --target $TRIPLET
Compiling my-demo v0.1.0 (/Users/alizj/code/rust/my-demo)
error: linking with `clang` failed: exit status: 1
= note: "clang" "-m64" "/tmp/rustc7DOG1Z/symbols.o" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-3d4809363f767eb8.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/x86_64-unknown-linux-gnu/debug/deps/my_demo-1bb942ce8cca92ee" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
= note: /usr/bin/ld: /Users/alizj/code/rust/my-demo/target/x86_64-unknown-linux-gnu/debug/deps/my_demo-1bb942ce8cca92ee.0erm4rq66fmuy7uh7yyx06r8m.rcgu.o: Relocations in generic ELF (EM: 62)
/usr/bin/ld: /Users/alizj/code/rust/my-demo/target/x86_64-unknown-linux-gnu/debug/deps/my_demo-1bb942ce8cca92ee.0erm4rq66fmuy7uh7yyx06r8m.rcgu.o: error adding symbols: file in wrong format
clang: error: linker command failed with exit code 1 (use -v to see invocation)
使用 gcc 交叉编译 #
OK: linker 必须指定为带架构前缀的 gcc 编译器:linker=x86_64-linux-gnu-gcc,且不能指定 –target 和 -fuse-ld 参数:
- gcc 不支持 –target 参数,也不需要支持(因为它本身就仅支持特定架构)
- gcc 的 -fuse-ld=xx 只能指定固定的值:bfd、gold、lld、mold;
- bfd、gold、mold 都是 by 架构的,但又不能指定对应架构的二进制,如 -fuse-ld=/usr/bin/x86_64-linux-gnu-ld,提示:note: x86_64-linux-gnu-gcc: error: unrecognized command-line option ‘-fuse-ld=/usr/bin/x86_64-linux-gnu-ld’
# OK
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc" cargo build --target $TRIPLET -v
FAILED:lld 虽然是多架构,但是只能被 host 架构的 gcc 调用,而不能被交叉编译器 x86_64-linux-gnu-gcc 使用(指定为 lld 后会报错:note: collect2: fatal error: cannot find ’ld’)。
- host 编译场景下,linker=gcc 时是可以指定的 link-arg=-fuse-ld=ldd 或 mold。
# FAILED:
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc -C link-arg=-fuse-ld=lld" cargo build --target $TRIPLET -v
error: linking with `x86_64-linux-gnu-gcc` failed: exit status: 1
= note: LC_ALL="C" PATH="/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/bin:/root/.cargo/bin:/root/go/bin:/usr/lib/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin" VSLANG="1033" "x86_64-linux-gnu-gcc" "-m64""/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-3d4809363f767eb8.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/root/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/Users/alizj/code/rust/my-demo/target/x86_64-unknown-linux-gnu/debug/deps/my_demo-1bb942ce8cca92ee" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-fuse-ld=lld"
= note: collect2: fatal error: cannot find 'ld'
compilation terminated.
# FAILED: 即使通过 link-arg=-Wl,-mx86_64 给 lld 传参 -mx86_64 也不行,还是提示:note: collect2: fatal error: cannot find 'ld'
root@ubuntu:/Users/alizj/code/rust/my-demo# TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc -C link-arg=-fuse-ld=lld -C link-arg=-Wl,-mx86_64" cargo build --target $TRIPLET -v
使用 musl 交叉编译 #
无论是 Host 编译,还是交叉编译,都应该使用 musl-gcc 或 musl-clang wrapper 脚本,它们内部会正确查找和链接 musl 提供的 libc 库和头文件。
Host 架构编译:
root@ubuntu:/Users/alizj/code/rust/my-demo# rustup target list |grep install
aarch64-unknown-linux-gnu (installed)
aarch64-unknown-linux-musl (installed)
x86_64-unknown-linux-gnu (installed)
x86_64-unknown-linux-musl (installed)
# OK:使用 musl-gcc 或 musl-clang wrapper 程序调用 gcc 或 clang
RUSTFLAGS="-C linker=musl-gcc" cargo build --target=aarch64-unknown-linux-musl
RUSTFLAGS="-C linker=aarch64-linux-musl-gcc -C link-arg=-fuse-ld=lld" cargo build --target=aarch64-unknown-linux-musl
# 以下方式是错误的:因为 gcc、clang 编译器都链接到的是 aarch64-linux-gnu 库,而不是 musl 库。
# 而且原生架构下,musl 的头文件都位于 aarch64-linux-gnu 目录下,文件时混淆的,所以通过设置 --sysroot 来执行 musl 自己目录是不行的,
# 必须使用 musl-gcc 或 musl-clang wrapper。
# cargo build --target=aarch64-unknown-linux-musl
# RUSTFLAGS="-C linker=clang" cargo build --target=aarch64-unknown-linux-musl
# RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=lld" cargo build --target=aarch64-unknown-linux-musl
# RUSTFLAGS="-C linker=gcc" cargo build --target=aarch64-unknown-linux-musl
# RUSTFLAGS="-C linker=gcc -C link-arg=-fuse-ld=lld" cargo build --target=aarch64-unknown-linux-musl
交叉编译:
- 前面 “clang 工具链” 一节分析过,clang 不能正确推断交叉编译架构 x86_64-unknown-linux-musl 的 sysroot(默认推断到了 x86_64-unknown-linux-gnu 的 sysroot),需要通过 –sysroot 参数明确指定。
# OK:使用 x86_64-linux-musl-gcc 或 aarch64-linux-musl-gcc 或着 *-linux-musl-clang 等 wrapper 脚本
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=x86_64-linux-musl-gcc" cargo build --target $TRIPLET -v
# OK:使用 clang 时,初步不是 musl-clang 编译器,则需要指定 --sysroot 参数(使用 musl-clang 或 *-musl-clang 时不需要)
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=musl-clang -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=lld -Clink-arg=--sysroot=/usr/local/musl/x86_64-linux-musl-cross -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=/usr/local/musl/x86_64-linux-musl-cross/bin/x86_64-linux-musl-ld -Clink-arg=--sysroot=/usr/local/musl/x86_64-linux-musl-cross -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
# 错误的、不建议的方式:它们会错误的使用 /usr/bin/../lib/gcc-cross/x86_64-linux-gnu 而不是 /usr/local/musl/x86_64-linux-musl-cross
#TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc" cargo build --target $TRIPLET -v
#TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=lld -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
#TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=/usr/bin/x86_64-linux-gnu-ld -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
Rust 程序编译链接总结 #
- 无论是 Host 架构编译还是交叉编译,都使用
-C linker=clang -Clink-arg=-fuse-ld=lld
。
- 因为交叉编译时 gcc 不支持 lld,而 lld 的链接速度比 ld 快!
- 对于交叉编译时额外指定
-Clink-arg=--target=$TRIPLET;
,如果是 musl,还需要指定-Clink-arg=--sysroot=/usr/local/musl/x86_64-linux-musl-cross
- 对于交叉编译,需要实现安装 rust target,gcc 类型的交叉编译工具链,clang 会自动查找和使用它们的库和头文件
Host 架构编译:
# gcc
cargo build
# 等效于
RUSTFLAGS="-C linker=cc" cargo build
RUSTFLAGS="-C linker=gcc" cargo build
# gcc 使用 lld,链接速度比默认的 ld 更快
RUSTFLAGS="-C linker=gcc -Clink-arg=-fuse-ld=lld" cargo build
# 使用 musl-gcc wrapper
RUSTFLAGS="-C linker=musl-gcc -Clink-arg=-fuse-ld=lld" cargo build --target=aarch64-unknown-linux-musl
# clang
RUSTFLAGS="-C linker=clang" cargo build
RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=lld" cargo build
RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=mold" cargo build
RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=/usr/bin/mold" cargo build
# 对于 clang,必须使用 musl-clang(为 clang 指定 --sysroot 也不行),且不能给它设置 ld,因为 musl-clang 固定使用自带的 ld.musl-clang
RUSTFLAGS="-C linker=musl-clang" cargo build
交叉编译(clang –target 的 triplet 和 rustc 的 –target 的 triplet 参数值一致。):
# gcc:必须指定 linker,不能指定 -fuse-ld
TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=x86_64-linux-gnu-gcc" cargo build --target $TRIPLET -v
# musl-gcc
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=musl-gcc" cargo build --target $TRIPLET -v
# clang:必须指定 linker、-fuse-ld 和 --target
TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=lld -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
TRIPLET=x86_64-unknown-linux-gnu; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=/usr/bin/x86_64-linux-gnu-ld -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
# clang + musl
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=clang -Clink-arg=-fuse-ld=lld -Clink-arg=--sysroot=/usr/local/musl/x86_64-linux-musl-cross -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
TRIPLET=x86_64-unknown-linux-musl; RUSTFLAGS="-C linker=musl-clang -Clink-arg=--target=$TRIPLET" cargo build --target $TRIPLET -v
参考 #
- https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/Specifying-Target-Triplets.html
- rustc –print target-list
- LLVM 定义的 triple:https://llvm.org/doxygen/classllvm_1_1Triple.html
- https://www.linuxfromscratch.org/lfs/view/development/partintro/toolchaintechnotes.html