同步操作将从 deepinwiki/wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
c / c++ 是 linux 中常见的编译语言。虽然我们不是开发人员,但是作为 linux 用户,经常要自己从源代码构建可用的应用程序,所以我们也需要理解 c / c++ 的源代码是怎么编译成可以执行的应用程序的。
基本过程:c / c++ 源代码.c --> 预编译 --> 汇编.s --> 目标文件.o --> 可执行文件
库: 一个或者多个目标文件可以组成库。分静态库 xxx.a 和动态库 xxx.so 两种。
程序: 程序是多个目标文件、静态库、动态库组合而成的可执行单元。
链接:多个目标文件组成库或程序的过程,叫链接。也可以细为动态链接 ld,静态链接 ar。
动态库: 动态库并不合并到程序中,而是在运行时从指定磁盘路径装载对应的动态库。这可以让多个程序共享一个动态库,但是也会面临丢失动态库文件,或者动态库版本不对,而导致程序无法运行的问题。这个被俗称为 dll 地狱,是一种常见的应用失败原因。
通过 clang 演示编译过程的例子:
# 预处理
# test.c 是 c 语言的源文件
# -E 表示只进行到预处理阶段
# -o 表示输出
clang -E test.c -o test.i
# 编译成汇编源文件
clang -S test.i -o test.s
# 汇编
clang -c test.s -o test.o
# 链接
clang test.o -o test
clang 或者 gcc 是一个集成一站式的编译工具,它内部实际会调用相关的工具。可以手动指定工具,这在一些特殊场合是有必要的。
其中,可能会用得比较多的是链接器这一部分。链接是比较复杂的,它有静态链接,动态链接,而动态链接在运行时还会影响到最终用户。
接下来的内容重点也放在这一部分。
c 语言的标准库是 libc,所谓标准库,就是这个语言编译的每一个程序,几乎都会用到的一组公共功能。而 libc 并不是只有一种,有不同的厂家推出不同的 libc,其中最常用的是 gcc 编译器自带的 libc.so.6 (glibc)。
不过 glibc 有些臃肿,就有人选择 libc.so (musl)。
链接器种类: ld, lld, gold
动态链接因为并不是直接整合到程序里面的,而是在程序运行时加载的,那么这个加载器就叫动态链接器。
运行时是一个 c 程序启动和关闭需要的基本功能。如果使用 gcc 或 clang 来编译,他会自动添加这些部分。如果是自己使用链接器,就需要自己添加他们。
musl 动态链接使用演示:
# musl 手动链接
# test.o 目标文件
# test 最终程序
# --dynamic-linker 指定动态链接器,因为是musl,所以选择 ld-musl-x86_64.so.1
# -L 搜索的目录,执行为 musl 的库路径 /usr/lib/musl/lib
# -lc 表示链接指定库,会自动修改为 libxxx.so,如果找不到会找 libxxx.a。所以这里就是 libc.so。如果想静态链接,那么可以改成 -l:libc.a
# -l:crt1.o 表示链接 crt1.o,这个形式会严格按照文件名链接
ld.lld test.o -o test -dynamic-linker /lib/ld-musl-x86_64.so.1 -L /usr/lib/musl/lib -lc -l:crt1.o -l:crti.o -l:crtn.o
# musl-clang 集成编译系统
# -static 表示静态链接所有库
musl-clang test.o -static
# zig cc 编译器
# 动态链接到 musl-libc
zig cc test.o -o test -Wl,-dynamic-linker=/lib/ld-musl-x86_64.so.1 -L /usr/lib/musl/lib
# 静态链接到 musl-libc
zig cc test.o -o test -nostdlib -Wl,-Bstatic -l:crt1.o -l:crti.o -L /usr/lib/musl/lib -lc -l:crtn.o
# 使用 zig 内置的 musl-libc
zig cc test.o -o test -target x86_64-linux-musl
c++ 语言的标准库是 libstdc++(gcc)或 libc++(clang)。
推荐链接顺序: crt1.o crti.o crtbegin.o [-L 搜索路径] [目标文件] [gcc libs] [C libs] [gcc libs] crtend.o crtn.o
注意, gcc 的 libstdc++ 库虽然支持静态链接,但是它和 gcc 的 libc 是强制依赖的。所以,使用 libstdc++ 时不要替换 glibc,只能联合使用。
另外, gcc 对静态链接 c++ 标准库的支持不好,请使用 g++。
演示:
# test.o 是 c 语言目标文件
# test2.o 是 c++ 语言目标文件
# -static 静态链接标准库
g++ test.o test2.o -static
# libstd++ / libgcc 是静态链接,而 libc 是动态链接的
g++ test.o test2.o -static-libstd++ -static-libgcc
# 效果同上
# -pie 位置无关程序(一种安全措施)
# Scrt1.o 是 PIE 版 crt1.o
# -Bstatic 表示往后静态链接
# -Bdynamic 表示往后优先动态链接
ld.lld -pie -l:Scrt1.o -l:crti.o -l:crtbeginS.o -L /usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/ -L /usr/lib test.o test2.o -Bstatic -lstdc++ -lgcc -lgcc_eh -Bdynamic -lc -lm -Bstatic -lgcc -lgcc_eh -l:crtendS.o -l:crtn.o --dynamic-linker /lib/ld-linux-x86-64.so.2
ldd a.out
# 显示:
linux-vdso.so.1 (0x00007ffec59ba000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f8d38600000)
/lib/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f8d38875000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f8d38519000)
也许你注意到推荐链接里面有个重复的地方,这是因为有些库会相互依赖。也就是 a 依赖 b,但 b 也会依赖 a,这时候,链接就会发生找不到定义。
要解决这个问题,可以添加一个搜索组,组成员会循环的解决内部的依赖关系,而不是默认的按顺序判断。
# -static 静态链接
# -nostdlib 不要自动链接标准库
# -Wl,--star-group 传递 --start-group 参数给链接器,创建一个搜索组
# -Wl,--end-group 组结束标志
gcc -l:crt1.o -l:crti.o -l:crtbegin.o test.o test2.o -static -nostdlib -lstdc++ -Wl,--start-group -lgcc -lgcc_eh -lc -Wl,--end-group -l:crtend.o -l:crtn.o
为什么要用静态链接,因为程序分 pie 和非 pie 和静态版本,它和 libc 是强相关的。如果 libc 是静态链接,其余部分也必须如此配合。而其他库就没有这个限制。如可以在动态程序里面支持静态链接 libstdc++.a 。
其他可能有用的 clang 编译参数:
glibc 虽然占据主流地位,但是太臃肿,它的大小差不多6m,而 musl libc 才不到 3m。尤其在一些 docker,嵌入式等平台,就很需要更精简的 c 库了。
为此,我们可能需要做两种情况的考虑。一个是编译为本机代码,只是替换为 musl libc。这种情况我们需要考虑的是,怎么不被当前本机环境影响,比如我们有些库可能会依赖 glibc,或者不小心引入了 glibc 版本的库。我们需要提供一个纯净的 musl libc 编译环境。
另一种情况我们要编译成别的平台的代码,这叫交叉编译,我们需要一个运行在本地,但是能产生目标平台代码的交叉编译环境。这比上一种情况更加复杂。
传统上,我们使用 gcc 或者 clang 这种工具链,需要配置复杂的交叉平台环境,但是现在有一个替代品,她就是 zig 语言编译器。这个编译器能支持 c 和 c++ 的编译,并且提供了相关目标平台的基本库(这在传统上是非常复杂,且容易出错的)。
目标平台三元组(target triple):
不过三元组的名字,并没有严格的统一标准,各个编译器采用的也许不一样。它主要是给编译器一个参考,其中最重要是指令架构和库标准,它决定了编译器会编译成那种指令,和链接哪种标准库。
# zig cc 和 clang 的用法一致
zig cc --version
# zig c++ 同时支持 c++ 和 c
# -target 指定目标平台。如: riscv64-linux-musl
# zig 的 musl 目标,会静态链接
zig c++ test.c test2.cpp -target riscv64-linux-musl
file a.out
# 显示:
a.out: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, not stripped
# 用 qemu 虚拟机运行程序,ok
qemu-riscv64 a.out
musl 一般是用于静态链接。但是也可以用做动态链接。这时候,它和 glibc 有一些不同。
配置搜索路径: /etc/ld-musl-x86_64.patch
工作原理: 首先使用 musl 编译的程序,会指定链接器: /lib/ld-musl-x86_64.so.1 。 这并不是一个一般的库,而是一个可执行程序,它就是负责加载程序需要的动态链接库的链接器。
那么它从哪里加载相关库呢?首先它读取程序文件中记录的相关动态库的名称(soname),但它并不包括路径。所以这就需要另外给出路径。一般在编译的时候,会添加一些路径到程序内部(-rpath 选项)。最终用户还能通过上述配置文件,自己添加路径。
如果只需要临时添加搜索路径,那么可以通过环境变量: LD_LIBRARY_PATH 指定。
# 路径以冒号分隔
LD_LIBRARY_PATH=/lib:/usr/lib ./a.out
为什么动态链接会经常出现问题。因为它将出错的时机留到了安装到最终用户电脑上,用户配置错误的路径。而且,不同链接器其实是不兼容的。用 musl 开发的库,用 glibc 的默认加载器就会出现错误。反之亦然。
新的新版本和旧版本未必是百分百兼容的。
因此,设计者就使用了不同命名来缓解这个问题。一个动态库包括三个名字:真实名(realname)、动态名(soname)、链接名(linkname)。
总结就是 soname 是动态加载器使用的链接文件。编译器会将这个名字写入库文件内部,和链接到它的所有程序和库内部。
linkname 只是为了编译参数方便和不用变更所使用的链接文件。
realname 是实际的库文件。三者配合下,库文件可以比较灵活的变更。
musl 只是一个 libc 库,这对于只用 c 库的情况是足够的,但是,linux 中大多数项目都用了 c++。 虽然 c++ 本身也会引用到 c 库,但是 gnu gcc 的 c++ 库是绑定自己的 glibc 库的,所以并不能简单混用。
因此,我们只能重新构建一个依赖 musl 的 c++ 库,这时候就面临选择,选择 libstd++ (gnu gcc) 还是 libc++ (llvm clang)。事实 libstd++ 对 glibc 的依赖过于紧密,所以选择 libc++。
libstdc++ 依赖 libgcc 运行时库。libc++ 对应的产物是 compiler-rt 。llvm 项目将这一部分分得更细, libc++ 可以选择 abi(二进制接口)的标准,它可以依赖 libc++abi 库(一个相对libstdc++abi 更加精细的 abi),同时将栈回溯技术分离成 libunwind 库。
默认情况,因为 gcc 是 linux 系统事实上的标准,clang 为了兼容,它默认是依赖 glibc 和 libstdc++ (libgcc)。因为 libgcc 是一个运行时,也就是 clang 编译出来的程序,都会带运行时。这就导致对 libgcc 有紧密的依赖。如果我们用 clang 来构建新的 compiler-rt 运行时,它也会依赖,这就是一个死循环。
因此,我们首先要创建一个不依赖 libgcc 的 clang, 然后用它构建 compiler-rt ,然后用这些和 musl 构建 libc++ libc++abi libunwind。最后,再用新的库构建全新的 clang,这就完成了一套基于 musl 的编译系统。
下面是编译方法。
llvm 是 clang 的母项目,它包含 libc++ 等组件。LLVM 采用 cmake 构建系统。
cmake 基本用法:
-L[A/H]
: 列出当前项目配置的参数# 下载 llvm 的镜像源
#
# 镜像源将加速我们下载的速度
git clone -depth=1 https://mirrors.tuna.tsinghua.edu.cn/git/llvm-project.git
# 进入 llvm 目录
cd llvm-project
# 配置项目
cmake -B build -G Ninja -S llvm \
-DLLVM_ENABLE_PROJECTS=clang \
-DLLVM_USE_LINKER=lld \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_BUILD_TYPE=Release \
-DCLANG_DEFAULT_RTLIB=compiler-rt \
-DCLANG_DEFAULT_CXX_STDLIB=libc++ \
-DCLANG_DEFAULT_LINKER=ld.lld \
-DCLANG_DEFAULT_UNWINDLIB="" \
-DLLVM_TARGETS_TO_BUILD="X86;RISCV" \
-DLLVM_DEFAULT_TARGET_TRIPLE="x86_64-unknown-linux-musl"
-DLLVM_TARGET_ARCH="native-unknown-linux-musl" \
-DCMAKE_C_FLAGS="" \
-DCMAKE_CXX_FLAGS="" \
-DLLVM_TARGETS_TO_BUILD="X86;RISCV" \
-DLIBCXX_HAS_MUSL_LIBC=yes \
-DLIBCXX_USE_COMPILER_RT=yes \
-DLIBCXX_CXX_ABI=libcxxabi \
-DLIBCXX_HAS_PTHREAD_API=yes \
-DLIBCXXABI_USE_LLVM_UNWINDER=yes \
-DLIBCXXABI_USE_COMPILER_RT=yes \
-DLIBUNWIND_USE_COMPILER_RT=yes \
-DCOMPILER_RT_DEFAULT_TARGET_TRIPLE=native-unknown-linux-musl \
-DCOMPILER_RT_USE_LLVM_UNWINDER=ON \
-DCOMPILER_RT_USE_BUILTINS_LIBRARY=ON
-DLLVM_ENABLE_EH=yes \
-DLLVM_ENABLE_RTTI=yes
# 构建
ninja -C build
# 安装到指定目录
DESTDIR=$(realpath ../llvm-root) fakeroot ninja -C build install
libc++ 是 clang 自带的 c++ 标准库,它的好处是比较独立,规范。libstdc++ 是 gnu gcc 的一部分,它和其余整合比较严密,所以为了配置 musl c 库来使用,最好用 libc++ 替换 libstdc++。
zig 编译器虽然能够构建出静态版本,但是如果我们想动态链接怎么办?这时候也许就需要我们自己去编译目标平台的 libc++ 库了。
# 配置项目
cmake -G Ninja -S runtimes -B build \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind;" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_FLAGS="--sysroot=/lib/musl -rtlib=compiler-rt -Wl,--as-needed" \
-DCMAKE_CXX_FLAGS="--sysroot=/lib/musl -rtlib=compiler-rt -Wl,--as-needed" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_TARGETS_TO_BUILD="X86;RISCV" \
-DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-unknown-linux-musl \
-DLIBCXX_HAS_MUSL_LIBC=YES \
-DLIBCXX_USE_COMPILER_RT=YES \
-DLIBCXX_CXX_ABI=libcxxabi \
-DLIBCXX_HAS_PTHREAD_API=YES \
-DLIBCXXABI_USE_LLVM_UNWINDER=YES \
-DLIBCXXABI_USE_COMPILER_RT=YES \
-DLIBUNWIND_USE_COMPILER_RT=YES
# 构建
ninja -C build
# 安装到指定目录
DESTDIR=$(realpath ../llvm-root) fakeroot ninja -C build install
-Wl,--dynamic-linker=/lib/ld-musl-x86_64.so.1
-DLLVM_USE_LINKER=ld \
--sysroot=$(realpath ../llvm-root/usr/local)
$(realpath ../llvm-root/usr/local/bin/clang)
-Wl,-dynamic-linker=/lib/ld-musl-x86_64.so.1
musl 作为编译目标没有 execinfo.h 文件,而编译 compiler-rt 需要这个。apline 系统中又一个 libexecinfo 库,借来用用。
项目地址: https://git.alpinelinux.org/aports/tree/main/libexecinfo?h=3.10-stable
# 下载相关文件(略)
# 解压缩
tar -xvf libexecinfo-1.1.tar.bz2
# 打补丁
cd libexecinfo-1.1
patch --verbose -p1 < ../10-execinfo.patch
patch --verbose -p1 < ../20-define-gnu-source.patch
patch --verbose -p1 < ../30-linux-makefile.patch
# 编译
# 动态库
musl-clang -shared execinfo.c stacktraverse.c -fuse-ld=lld -rtlib=compiler-rt --sysroot /lib/musl -Wl,-soname,libexecinfo.so.1 -o libexecinfo.so.1
ln -s libexecinfo.so.1 libexecinfo.so
# 静态库
musl-clang -c execinfo.c stacktraverse.c --sysroot /lib/musl
llvm-ar rcs libexecinfo.a execinfo.o stacktraverse.o
# 安装
cp -v *.h $(realpath ../llvm-root/usr/local/include)
cp -v *.a $(realpath ../llvm-root/usr/local/lib)
cp -v *.so* $(realpath ../llvm-root/usr/local/lib)
# 配置项目
cmake -G Ninja -S runtimes -B build \
-DLLVM_ENABLE_RUNTIMES="compiler-rt" \
-DCMAKE_C_COMPILER=zig-cc \
-DCMAKE_CXX_COMPILER=zig-c++ \
-DCMAKE_CXX_FLAGS="" \
-DCMAKE_BUILD_TYPE=Release \
-DCOMPILER_RT_USE_LIBCXX=ON \
-DLLVM_TARGETS_TO_BUILD="X86;RISCV" \
-DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-unknown-linux-musl \
-DCOMPILER_RT_USE_BUILTINS_LIBRARY=ON \
-DCMAKE_C_COMPILER_TARGET="x86_64-unknown-linux-musl" \
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON
-DCOMPILER_RT_DEFAULT_TARGET_TRIPLE=x86_64-unknown-linux-musl
-DCMAKE_C_FLAGS="--sysroot=/lib/musl -L $(realpath ../llvm-root/usr/local/lib) -isystem $(realpath ../llvm-root/usr/local/include) -rtlib=compiler-rt -stdlib=libc++ -lexecinfo -Wl,--as-needed" \
-DCOMPILER_RT_USE_BUILTINS_LIBRARY=ON \
-DCMAKE_C_COMPILER_TARGET="x86_64-unknown-linux-musl" \
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON
\-DCOMPILER_RT_USE_LLVM_UNWINDER=ON \
-DLLVM_USE_LINKER=lld \
-DLIBCXXABI_USE_LLVM_UNWINDER=ON \
-DLIBCXX_HAS_MUSL_LIBC=yes \
-DLLVM_ENABLE_LIBCXX=ON \
-DLLVM_STATIC_LINK_CXX_STDLIB=ON \
-DCOMPILER_RT_USE_LLVM_UNWINDER=ON \
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。