GCC Multilib

Table of Contents

1. GCC Multilib

1.1. gcc

gcc 需要解决两个和 multilib 相关的问题:

  1. 使用 gcc 时需要根据 march/mabi 找到对应的 multilib dir, 以找到正确的 startfile/libgcc
  2. 编译 gcc 时需要编译出前面用到的 multilib dir

1.1.1. 运行时查找 multilib dir

解决问题 1 依赖于 makefile 中定义的几个变量:

  1. MULTILIB_OPTIONS
  2. MULTILIB_DIRNAMES
  3. MULTILIB_REQUIRED
  4. MULTILIB_REUSE

这几个变量主要决定了:

  1. 针对哪些 march/mabi 的组合需要编译出 startfile/libgcc
  2. 某个 march/mabi 的组合需要使用哪个 multilib dir

对 riscv 来说, 定义这些变量比较繁锁, gcc 提供了一个 multilib-generator 脚本来生成这些变量. config.gcc 中使用 multilib-generator 针对不同的 target 预先生成了几套例如 t-linux-multilib, t-elf-multilib 等, 用户可以通过 --with-multilib-generator 向 multilib-generator 传递特定的参数, 生成自己需要的配置

例如:

# This file was generated by multilib-generator with the command:
#  ./multilib-generator rv32imac-ilp32-rv32ima,rv32imaf,rv32imafd,rv32imafc,rv32imafdc-
#  rv32imafdc-ilp32d-rv32imafd- rv64imac-lp64-rv64ima,rv64imaf,rv64imafd,rv64imafc,rv64imafdc-
#  rv64imafdc-lp64d-rv64imafd-

MULTILIB_OPTIONS =\
march=rv32imac/march=rv32ima/march=rv32imaf/march=rv32imafd/march=rv32imafc/\
march=rv32imafdc/march=rv32g/march=rv32gc/march=rv64imac/march=rv64ima/march=rv64imaf/\
march=rv64imafd/march=rv64imafc/march=rv64imafdc/march=rv64g/march=rv64gc \
mabi=ilp32/mabi=ilp32d/mabi=lp64/mabi=lp64d

# MULTILIB_DIRNAMES 与 MULTILIB_OPTIONS 是一一对应的
MULTILIB_DIRNAMES = rv32imac \
rv32ima \
rv32imaf \
rv32imafd \
... \
rv64gc \
ilp32 \
ilp32d \
lp64 \
lp64d

# MULTILIB_REQUIRED 表示仅针对以下组合生成 multlib, 而不是针对 MULTILIB_OPTIONS
# 中的每个组合(cartesian product) 都生成 multilib
MULTILIB_REQUIRED = march=rv32imac/mabi=ilp32 \
march=rv32imafdc/mabi=ilp32d \
march=rv64imac/mabi=lp64 \
march=rv64imafdc/mabi=lp64d

# rv32ima/ilp32 会 fallback 到 rv32imac/ilp32
MULTILIB_REUSE = march.rv32imac/mabi.ilp32=march.rv32ima/mabi.ilp32 \
march.rv32imac/mabi.ilp32=march.rv32imaf/mabi.ilp32 \
march.rv32imac/mabi.ilp32=march.rv32imafd/mabi.ilp32 \
... \
march.rv64imafdc/mabi.lp64d=march.rv64imafd/mabi.lp64d \
march.rv64imafdc/mabi.lp64d=march.rv64gc/mabi.lp64d \
march.rv64imafdc/mabi.lp64d=march.rv64g/mabi.lp64d

通过 gcc -print-multi-os-directory 可以确定运行时 march/mabi 会找到哪个 multilib dir.

以上 makefile 中定义的的 MULTILIB_XXX 做为运行时参数会通过 spec 保存在 gcc 中, gcc 在运行时查找 startfile(通过 find_file)或构造 library path (-L xxx) 时, 会处理这些 spec:

  1. 先通过 add_prefix 确定 startfile_prefixes, 包括:
    • gcc -B 指定的 path
    • SYSROOT
    • GCC_EXEC_PREFIX
  2. 通过 set_multilib_dir 解析 spec 确定 multilib dir
  3. for_each_path 会在 startfile_prefixes 的路径后加上 multilib dir 得到最终的路径, 例如 <SYSROOT>/lib32/ilp32
1.1.1.1. startfile_prefix_spec

startfile_prefixes 包含了硬编码的 startfile_prefix_spec

#define STARTFILE_PREFIX_SPEC 			\
   "/lib" XLEN_SPEC "/" ABI_SPEC "/ "		\
   "/usr/lib" XLEN_SPEC "/" ABI_SPEC "/ "	\
   "/lib/ "					\
   "/usr/lib/ "

#define XLEN_SPEC \
  "%{march=rv32*:32}" \
  "%{march=rv64*:64}" \

#define ABI_SPEC \
  "%{mabi=ilp32:ilp32}" \
  "%{mabi=ilp32e:ilp32e}" \
  "%{mabi=ilp32f:ilp32f}" \
  "%{mabi=ilp32d:ilp32d}" \
  "%{mabi=lp64:lp64}" \
  "%{mabi=lp64f:lp64f}" \
  "%{mabi=lp64d:lp64d}" \

gcc 会把 sysroot/lib32/ilp32e 加入到 startfile_prefixes

if (*startfile_prefix_spec != 0 &&
    do_spec_2(startfile_prefix_spec, NULL) == 0 &&
    do_spec_1(" ", 0, NULL) == 0) {
    for (const char *arg : argbuf)
        add_sysrooted_prefix(
            &startfile_prefixes, arg, "BINUTILS", PREFIX_PRIORITY_LAST, 0, 1);
}

由于 multlib dir 会被加到 startfile_prefixes 之后, 导致 print-search-dir 时有时会看到这种奇怪的 path: sysroot/lib32/ilp32e/lib32/ilp32. 通过测试, startfile_prefix_spec 看起来并没有什么用处, gcc 通过 multilib spec 在 sysroot 或 gcc_exec_prefix 中能正常找到 startfile

1.1.1.2. linker_spec

dynamic linker 属于 libc, 但它有不一样的 multilib 配置

  1. 它的位置在 sysroot/lib/ld-linux-{march}-{mabi}.so.1
  2. gcc 在 linker_spec 硬编码了 dynmaic linker 的位置

    #define GLIBC_DYNAMIC_LINKER "/lib/ld-linux-riscv" XLEN_SPEC "-" ABI_SPEC ".so.1"
    

1.1.2. 编译出 multilib dir

编译完 gcc 后, 通过 gcc -print-multi-lib 可以输出 march/mabi 对应的 multilib dir, 例如:

.;
lib32/ilp32;@march=rv32imac@mabi=ilp32
lib32/ilp32d;@march=rv32imafdc@mabi=ilp32d
lib64/lp64;@march=rv64imac@mabi=lp64
lib64/lp64d;@march=rv64imafdc@mabi=lp64d

实际就是 MULTILIB_REQUIRED. 然后编译系统会解析这个输出, 为 libgcc 等构造多个 build 目录, 并在对应的 Makefile 中生成 march=rv32imac 这些 flag, 例如:

riscv64-unknown-linux-gnu/lib32/ilp32/libgcc/Makefile
riscv64-unknown-linux-gnu/lib32/ilp32d/libgcc/Makefile
riscv64-unknown-linux-gnu/lib64/lp64/libgcc/Makefile
riscv64-unknown-linux-gnu/lib64/lp64d/libgcc/Makefile
riscv64-unknown-linux-gnu/libgcc/Makefile

相关代码主要在 config-ml.in

1.1.3. 总结

  • makefile 指定的 MULTILIB_XXX 保存在 gcc spec 中
  • gcc 运行时主要通过 multilib_reuse spec 确定 march/mabi 对应的 multilib dir, 查找 startfile 或生成 library path 时会考虑这个 multilib dir
  • 编译 startfile/libgcc 时通过 gcc --print-multi-lib 确定需要输出的 multilib dir 和 flag

1.2. libc

1.2.1. glibc

glibc 并没有像 gcc 一样内置 multilib 支持, riscv-gnu-toolchain 是通过主动编译出多个 glibc 并安装到对应的目录实现 multilib

$> cat configure
...
if test "x$enable_multilib" != xno; then :
  glibc_multilib_names="rv32imac-ilp32 rv32imafdc-ilp32d rv64imac-lp64 rv64imafdc-lp64d"

else
  glibc_multilib_names="$with_arch-$with_abi"
fi
...

$> cat Makefile.in

GLIBC_MULTILIB_NAMES := @glibc_multilib_names@
build-libc: $(addprefix stamps/build-glibc-linux-,$(GLIBC_MULTILIB_NAMES))

stamps/build-glibc-linux-%:
ifeq ($(MULTILIB_FLAGS),--enable-multilib)
    $(eval $@_ARCH := $(word 4,$(subst -, ,$@)))
    $(eval $@_ABI := $(word 5,$(subst -, ,$@)))
else
    $(eval $@_ARCH := )
    $(eval $@_ABI := )
endif
    $(eval $@_LIBDIRSUFFIX := $(if $($@_ABI),$(shell echo $($@_ARCH) | sed 's/.*rv\([0-9]*\).*/\1/')/$($@_ABI),))
    $(eval $@_CFLAGS := $(if $($@_ABI),-march=$($@_ARCH) -mabi=$($@_ABI),))
    $(eval $@_LIBDIROPTS := $(if $@_LIBDIRSUFFIX,--libdir=/usr/lib$($@_LIBDIRSUFFIX) libc_cv_slibdir=/lib$($@_LIBDIRSUFFIX) libc_cv_rtlddir=/lib,))
    cd $(notdir $@) && \
        .. \
        CFLAGS="$(CFLAGS_FOR_TARGET) -O2 $($@_CFLAGS)" \
        $</configure \
        .. \
        $($@_LIBDIROPTS)

可见 riscv-gnu-toolchain 会固定的编译几个 glibc 的 multilib, 和编译 gcc 时使用的 multilib 配置无关

1.2.2. newlib

newlib 的 build 系统使用了和 gcc 相同的 config-ml.in, 因此它依赖于 gcc --print-multi-lib 从而能保证与 gcc 的 multilib 配置一致

1.3. debug

gcc --print-search-dirs
gcc --print-file-name crt1.o
gcc --print-multi-lib
gcc --print-multi-os-directory
gcc -v

Author: [email protected]
Date: 2024-01-02 Tue 16:33
Last updated: 2024-02-01 Thu 14:04

知识共享许可协议