Ch22.3: GCC: Build, Host, Target
1. Three Flags, One Build System
GCC's top-level configure accepts --build, --host and
--target. They are not synonyms and none of them is "the platform":
| Flag | Meaning | Who runs the output |
|---|---|---|
--build= |
The machine that compiles GCC | The build tools produced during make |
--host= |
The machine where the installed GCC runs | The installed gcc, cc1plus, collect2 |
--target= |
The machine whose code GCC emits | The programs you compile later |
Defaults: --host defaults to --build, and --target
defaults to --host. So a normal native build needs no flags; a cross compiler
needs --target; a Canadian build needs --host as well.
--build is usually set explicitly anyway, because config.guess can
guess a different canonical name than the one you use for your other toolchains.
The build system keeps three compilers apart internally, and this is the part that matters:
-
CC_FOR_BUILD: the compiler for programs that run during the build. It must run onbuild. This is the native compiler on the build machine. -
CC: the compiler used to compile GCC's own source into the installed compiler. It must produce programs forhost. -
CC_FOR_TARGET: the compiler used to build target libraries such aslibgccandlibstdc++. It must produce programs fortarget, and it must run on the build machine during the build.
In a native build all three are the same program. In a cross build CC and
CC_FOR_BUILD are the same and CC_FOR_TARGET differs. In a Canadian
build all three can differ, and a build that conflates them produces a compiler that runs on
the wrong machine.
2. Two Compilers Before You Can Build One
To produce a compiler that runs on aarch64-linux-gnu from an
x86_64-linux-gnu machine, you first need an
x86_64-linux-gnu → aarch64-linux-gnu cross compiler. That compiler runs on
the build machine and emits AArch64 code, so it can compile:
- the compiler binary that will later run on the AArch64 host, and
- the AArch64
libgccandlibstdc++that the new compiler will use.
In a Canadian native build, host equals target, so this one cross compiler does both
jobs. In a Canadian cross build, host and target differ, and you need two:
build→host for the compiler binary and build→target for the
target libraries. For a crossback (build = target), the second is the native
compiler; for an ordinary Canadian cross it is a second cross compiler. The reason is not
only the target runtime: GCC is itself a C++ program, so the host it will run on needs a
complete C++ sysroot. The build→host compiler is what produces a compiler binary linked
against that host C++ runtime. The build order is therefore:
1. build -> host cross toolchain (the compiler that builds the host compiler)
2. build -> target compiler (only if host != target; builds target libs)
3. host -> target toolchain (the product; configure --host=HOST --target=TARGET)
This is exactly the order used by the reference scripts in
toolchainbuildscripts.
The triplet list is a list of (build, host, target) rows, and
build_common.sh builds the prerequisites before the row itself:
# from gcc/build_common.sh
if [[ ${BUILD_GCC_TRIPLET} != ${HOST_GCC_TRIPLET} ]]; then
# canadian
build_toolchain $BUILD_GCC_TRIPLET $HOST_GCC_TRIPLET
if [[ ${BUILD_GCC_TRIPLET} != ${TARGET_GCC_TRIPLET} &&
${HOST_GCC_TRIPLET} != ${TARGET_GCC_TRIPLET} ]]; then
# canadian cross (non crossback)
build_toolchain $BUILD_GCC_TRIPLET $TARGET_GCC_TRIPLET
fi
fi
build_toolchain $HOST_GCC_TRIPLET $TARGET_GCC_TRIPLET
build_toolchain H T configures with
--build=$BUILD_TRIPLET --host=H --target=T,
installing into $ROOT/H/T. The final call installs into
$ROOT/HOST/TARGET. That prefix convention is what makes the pieces findable:
the cross compiler is at $ROOT/$BUILD/$HOST/bin/$HOST-gcc, and the product is at
$ROOT/$HOST/$TARGET/bin.
3. A Canadian Native Walkthrough
The rest of this section is the concrete recipe for
build=x86_64-linux-gnu, host=target=aarch64-linux-gnu. It is the
same sequence the scripts automate, with the bookkeeping made explicit.
BUILD=x86_64-linux-gnu
HOST=aarch64-linux-gnu
TARGET=$HOST
ROOT=$HOME/toolchains/gnu
CROSS=$ROOT/$BUILD/$HOST # runs on BUILD, targets HOST
FINAL=$ROOT/$HOST/$TARGET # runs on HOST, targets TARGET
SRC=$HOME/toolchains_build
export PATH=$CROSS/bin:$PATH
Step 1: binutils for the cross compiler
mkdir -p $SRC/build/binutils-cross && cd $SRC/build/binutils-cross
$SRC/binutils-gdb/configure \
--build=$BUILD --host=$BUILD --target=$TARGET \
--prefix=$CROSS \
--disable-nls --disable-werror --disable-tui --without-debuginfod
make -j"$(nproc)"
make install-strip
This produces $TARGET-as, $TARGET-ld, $TARGET-ar,
$TARGET-strip and the rest, all running on the build machine. Do not skip this:
a compiler installation without a matching assembler and linker is not a toolchain.
Step 2: stage-1 GCC, no libc yet
mkdir -p $SRC/build/gcc-stage1 && cd $SRC/build/gcc-stage1
$SRC/gcc/configure \
--build=$BUILD --host=$BUILD --target=$TARGET \
--prefix=$CROSS \
--disable-bootstrap --disable-nls --disable-werror \
--enable-languages=c \
--without-headers --disable-threads --disable-shared \
--disable-libssp --disable-libquadmath --disable-libatomic \
--disable-libsanitizer --disable-libstdcxx --disable-multilib
make -j"$(nproc)" all-gcc all-target-libgcc
make install-strip-gcc install-strip-target-libgcc
This GCC can compile C and can build libgcc, but it has no target headers and no
target C library. That is the point: a libc needs a compiler, and the compiler needs a libc.
The stage-1 compiler breaks the cycle.
Step 3: kernel headers and libc into the sysroot
# Linux UAPI headers
make -C $SRC/linux headers_install ARCH=arm64 \
INSTALL_HDR_PATH=$CROSS/$TARGET
# glibc
mkdir -p $SRC/build/glibc && cd $SRC/build/glibc
export CC=$TARGET-gcc
export CXX=$TARGET-g++
$SRC/glibc/configure \
--build=$BUILD --host=$TARGET \
--prefix=$PWD/install \
--with-headers=$CROSS/$TARGET/include \
--disable-nls --disable-werror --without-selinux
make -j"$(nproc)"
make install
cp -a install/. $CROSS/$TARGET/
glibc's libc.so is often a linker script containing absolute paths. If you
install to a temporary prefix and copy, those paths still point at the temporary directory.
Either install directly with --prefix=$CROSS/$TARGET, or rewrite the three small
linker scripts (libc.so, libm.so) after copying. The reference
scripts do the second and delete the build prefix from the scripts.
For musl the flow is shorter, and for Android/Windows/macOS you usually start from the SDK's
sysroot instead of building libc at all. The invariant is the same: headers in
$CROSS/$TARGET/include, libraries in $CROSS/$TARGET/lib,
crt1.o and friends in the same sysroot.
Step 4: stage-2 GCC, now hosted
mkdir -p $SRC/build/gcc-stage2 && cd $SRC/build/gcc-stage2
$SRC/gcc/configure \
--build=$BUILD --host=$BUILD --target=$TARGET \
--prefix=$CROSS \
--disable-bootstrap --disable-nls --disable-werror \
--enable-languages=c,c++ \
--enable-shared --enable-threads=posix \
--disable-multilib
make -j"$(nproc)"
make install-strip
Now $CROSS/bin/$TARGET-g++ exists, libstdc++ is built for AArch64,
and the cross toolchain is complete. If you only wanted a cross compiler, you are done.
Step 5: binutils for the host compiler
mkdir -p $SRC/build/binutils-host && cd $SRC/build/binutils-host
STRIP=$HOST-strip \
$SRC/binutils-gdb/configure \
--build=$BUILD --host=$HOST --target=$TARGET \
--prefix=$FINAL \
--disable-nls --disable-werror --disable-tui --without-debuginfod
make -j"$(nproc)"
make install-strip
This time the tools run on the AArch64 host. The make install-strip step needs an
$HOST-strip that runs on the build machine; because host equals target here,
$TARGET-strip from step 1 is that program. In a Canadian cross build it is not,
and you must point STRIP at the build→host toolchain's strip.
Step 6: the host GCC
# the product needs its own copy of the target sysroot
mkdir -p $FINAL/$TARGET
cp -a $CROSS/$TARGET/. $FINAL/$TARGET/
# GCC may look for target headers in $FINAL/$TARGET/sys-include during
# the build; the reference scripts create this temporary symlink.
mkdir -p $FINAL/$TARGET
ln -sfn $CROSS/$TARGET/include $FINAL/$TARGET/sys-include
mkdir -p $SRC/build/gcc-host && cd $SRC/build/gcc-host
export CC=$HOST-gcc
export CXX=$HOST-g++
export CC_FOR_BUILD=$BUILD-gcc
export STRIP=$HOST-strip
export ac_cv_path_STRIP_FOR_TARGET=$TARGET-strip
$SRC/gcc/configure \
--build=$BUILD --host=$HOST --target=$TARGET \
--prefix=$FINAL \
--disable-bootstrap --disable-nls --disable-werror \
--enable-languages=c,c++ \
--enable-shared --enable-threads=posix \
--disable-multilib
make -j"$(nproc)"
make install-strip
rm -f $FINAL/$TARGET/sys-include
Read the three compiler variables carefully:
-
CC=$HOST-gccis the cross compiler from step 4. It runs on the build machine and produces AArch64 code, so it can compile the GCC binary that will run on AArch64. -
CC_FOR_BUILD=$BUILD-gccis the ordinary native compiler. It builds the generators that run duringmake. -
CC_FOR_TARGETdefaults to$TARGET-gcc. Because host equals target, it is the same cross compiler asCC; it builds the AArch64libgccandlibstdc++for the product.
In-tree gmp, mpfr, mpc and isl are host
libraries: the compiler links them and runs on the host. They are built with
CC, not with CC_FOR_TARGET. If you provide them externally, build
them for the host and pass --with-gmp, --with-mpfr,
--with-mpc.
4. Why the Sysroot Appears Twice
The cross toolchain at $CROSS needs a sysroot to build its own
libstdc++; the final toolchain at $FINAL needs a sysroot because
that is where its headers and libraries must be on the host. They are two copies:
$CROSS/$TARGET and $FINAL/$TARGET. The reference scripts install
libc separately for each prefix, which looks redundant until you remember that the two
compilers have different lifetimes. The cross toolchain is a build tool and can be deleted;
the final toolchain is the product and must be self-contained.
During the Canadian build, GCC also wants to find target headers under
$FINAL/$TARGET/sys-include. The scripts create a temporary symlink from
$FINAL/$TARGET/$TARGET/sys-include to the cross toolchain's
include directory, then remove it after installation. This is a build-time
workaround for GCC's default include layout, not something you should ship.
5. Canadian Cross, and the Crossback Quirk
If host and target differ, add the build→target compiler and set
CC_FOR_TARGET explicitly. In an ordinary Canadian cross this is a second cross
toolchain; in a crossback it is the native compiler, because build equals target.
# build -> target cross toolchain, installed at $ROOT/$BUILD/$TARGET
build_toolchain $BUILD $TARGET
# the Canadian cross product
export CC=$HOST-gcc
export CXX=$HOST-g++
export CC_FOR_TARGET=$TARGET-gcc
export CXX_FOR_TARGET=$TARGET-g++
export CC_FOR_BUILD=$BUILD-gcc
configure --build=$BUILD --host=$HOST --target=$TARGET ...
The roles are now distinct: CC produces the host compiler binary,
CC_FOR_TARGET produces the target runtime, and CC_FOR_BUILD
produces the build-time generators. Mixing up CC and CC_FOR_TARGET
is the classic failure: the build succeeds, and the installed compiler either cannot execute
on the host or emits code for the wrong machine.
Crossback is the special case of Canadian cross where
build = target ≠ host. GCC's
build system has a known quirk here: it may try to use cc instead of
gcc for CC_FOR_TARGET, and cc may not exist or may be
the wrong compiler. The reference scripts patch it explicitly:
if [[ "$BUILD_TRIPLET" == "$target_triplet" &&
"$BUILD_TRIPLET" != "$host_triplet" ]]; then
configure_env_vars+=(CC_FOR_TARGET="${target_triplet}-gcc")
fi
That is a good example of the general rule: in a Canadian build, never let a default choose a
compiler. Set CC, CC_FOR_BUILD and CC_FOR_TARGET by
name.
6. Freestanding Targets
For x86_64-elf, arm-none-eabi or
wasm32-unknown-unknown, there is no libc step. The compiler is configured
without a hosted C++ library and without target headers:
$SRC/gcc/configure \
--build=$BUILD --host=$BUILD --target=$TARGET \
--prefix=$PREFIX \
--without-headers --disable-hosted-libstdcxx \
--disable-threads --disable-shared \
--disable-libssp --disable-libquadmath --disable-libatomic \
--disable-libsanitizer \
--enable-languages=c,c++ --disable-multilib
make -j"$(nproc)" all-gcc all-target-libgcc
make install-strip-gcc install-strip-target-libgcc
libgcc still exists: it contains the integer helpers, the unwinder that the
compiler may need, and the soft-float routines. What does not exist is a C library, a
crt1.o, a dynamic loader, or a hosted libstdc++. The program
supplies its own entry point and its own memory and I/O primitives, which is what
Ch20 is about. The same toolchain can also be built for
wasm32-wasip1 when you provide a WASI sysroot; that is the configuration used in
Ch21.3.
7. Checking the Result
$FINAL/bin/gcc -v
# Build: x86_64-linux-gnu
# Host: aarch64-linux-gnu
# Target: aarch64-linux-gnu
$FINAL/bin/gcc -dumpmachine
# aarch64-linux-gnu
$FINAL/bin/gcc -print-sysroot
# empty or $FINAL/$TARGET, depending on how GCC was configured
$FINAL/bin/gcc -print-search-dirs
$FINAL/bin/gcc -v -E -x c /dev/null
# the include entries must be under $FINAL/$TARGET
Then compile a trivial program and inspect the link line with
-Wl,--verbose. It should find crt1.o and libc.so inside
$FINAL/$TARGET. If it finds the build machine's
/usr/lib/x86_64-linux-gnu, the sysroot is wrong, and the resulting binary is not
an AArch64 program.
The full automation, including the phase files, the sys-include workaround, crossback
handling, freestanding targets and packaging, is in
trcrsired/toolchainbuildscripts.
Read gcc/build_common.sh and gcc/triplet-*.sh after this page; the
script is dense, but every branch now has a name.