Last active
February 10, 2025 07:06
-
-
Save ascopes/a1dccf3ddd53bd8b33c4d66efea6b159 to your computer and use it in GitHub Desktop.
Creates a GNU cross-compiler consisting of binutils, gcc, g++, and gdb for a given architecture. Useful for OS development. Requires podman or docker locally.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -o errexit | |
| set -o nounset | |
| [[ -n ${DEBUG+defined} ]] && set -o xtrace | |
| function container() { | |
| if docker --version > /dev/null 2>&1; then | |
| local command=(docker "${@}") | |
| elif podman --version > /dev/null 2>&1; then | |
| local command=(podman "${@}") | |
| else | |
| echo "ERROR: Cannot find docker or podman" | |
| return 2 | |
| fi | |
| echo "Running ${command[@]}" >&2 | |
| "${command[@]}" | |
| } | |
| function export_if_undefined() { | |
| local variable_name=$1 | |
| local default_value=$2 | |
| if [[ -z ${!variable_name+defined} ]]; then | |
| export "${variable_name}=${default_value}" | |
| fi | |
| } | |
| function make_shell_script() { | |
| echo "#!/usr/bin/env bash" | |
| echo "set -o errexit" | |
| echo "set -o nounset" | |
| echo "set -o xtrace" | |
| cat | |
| } | |
| function run_in_container() { | |
| local file_name; file_name=$(uuidgen)-$(date +%s).sh | |
| make_shell_script > "build/${file_name}" | |
| chmod +x "build/${file_name}" | |
| container run \ | |
| --mount "type=bind,src=$(pwd),dst=/workspace" \ | |
| --pid host \ | |
| --rm \ | |
| --tty \ | |
| --user "$(id -u "${USER}"):$(id -g "${USER}")" \ | |
| "${image_name}" \ | |
| "build/${file_name}" | |
| } | |
| function usage() { | |
| echo "USAGE: ${BASH_SOURCE[0]} -a <arch> [-h]" | |
| echo "Build GCC, GDB, and Binutils for the given architecture." | |
| echo "" | |
| echo "Arguments:" | |
| echo " -a <arch> The architecture to target (e.g. i686-elf)." | |
| echo " -h Show this message, then exit." | |
| echo "" | |
| echo "Environment variables:" | |
| echo " BINUTILS_VERSION Override the version of Binutils to build." | |
| echo " DEBUG Define to run this script with debug logs." | |
| echo " GCC_VERSION Override the version of GCC to build." | |
| echo " GDB_VERSION Override the version of GDB to build." | |
| echo "" | |
| } | |
| while getopts ":a:h" opt; do | |
| case "${opt}" in | |
| a) readonly arch=${OPTARG} ;; | |
| h) usage; exit 0 ;; | |
| ?) echo "ERROR: Argument -${OPTARG} requires a value."; usage; exit 1 ;; | |
| :) echo "ERROR: Argument -${OPTARG} was not recognised."; usage; exit 1 ;; | |
| esac | |
| done | |
| for required_arg in arch; do | |
| if [[ -z ${!required_arg+defined} ]]; then | |
| echo "ERROR: Missing required argument '${required_arg}'." | |
| usage | |
| exit 1 | |
| fi | |
| done | |
| export_if_undefined BINUTILS_VERSION 2.43 | |
| export_if_undefined GCC_VERSION 14.2.0 | |
| export_if_undefined GDB_VERSION 16.2 | |
| readonly image_name="gnu-toolchain:latest" | |
| readonly binutils_url=https://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VERSION}.tar.gz | |
| readonly gcc_url=https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VERSION}/gcc-${GCC_VERSION}.tar.gz | |
| readonly gdb_url=https://ftp.gnu.org/gnu/gdb/gdb-${GDB_VERSION}.tar.gz | |
| cd "$(dirname "${BASH_SOURCE[0]}")" | |
| mkdir -pv src/ build/ out/ | |
| echo "Generating Dockerfile" | |
| cat >> src/Dockerfile <<'EOF' | |
| FROM public.ecr.aws/debian/debian:unstable-slim | |
| RUN apt-get update \ | |
| && apt-get install -qy \ | |
| bash \ | |
| bison \ | |
| build-essential \ | |
| curl \ | |
| flex \ | |
| gzip \ | |
| libgmp3-dev \ | |
| libisl-dev \ | |
| libmpc-dev \ | |
| libmpfr-dev \ | |
| tar \ | |
| texinfo \ | |
| && chsh -s "$(which bash)" \ | |
| && mkdir /workspace \ | |
| && chmod -R a+rwx /workspace | |
| WORKDIR /workspace | |
| ENTRYPOINT ["/usr/bin/env", "bash"] | |
| EOF | |
| echo "Building container with build tools to generate the toolchain..." | |
| container build . --file src/Dockerfile --tag "${image_name}" | |
| # Download resources. | |
| run_in_container <<-EOF | |
| cd src | |
| for url in "${binutils_url}" "${gcc_url}" "${gdb_url}"; do | |
| echo "Downloading and extracting \${url}..." | |
| curl -L --fail-with-body "\${url}" | tar xz | |
| done | |
| EOF | |
| # Build binutils | |
| run_in_container <<-EOF | |
| echo "Building binutils..." | |
| mkdir -pv build/binutils | |
| export PREFIX="\$(pwd)/out" | |
| export TARGET=${arch} | |
| export PATH="\$PREFIX/bin:\$PATH" | |
| cd build/binutils | |
| ../../src/binutils-${BINUTILS_VERSION}/configure \ | |
| --target=${arch} \ | |
| --prefix="\${PREFIX}" \ | |
| --with-sysroot \ | |
| --disable-nls \ | |
| --disable-werror | |
| make -j $(($(nproc) * 2)) | |
| make install | |
| EOF | |
| # Build gdb | |
| run_in_container <<-EOF | |
| echo "Building gdb..." | |
| mkdir -pv build/gdb | |
| export PREFIX="\$(pwd)/out" | |
| export TARGET=${arch} | |
| export PATH="\$PREFIX/bin:\$PATH" | |
| cd build/gdb | |
| ../../src/gdb-${GDB_VERSION}/configure \ | |
| --target=${arch} \ | |
| --prefix="\${PREFIX}" \ | |
| --disable-werror | |
| make -j $(($(nproc) * 2)) | |
| make install | |
| EOF | |
| # Download gcc dependencies | |
| run_in_container <<-EOF | |
| echo "Downloading GCC dependencies..." | |
| cd src/gcc-${GCC_VERSION} | |
| contrib/download_prerequisites | |
| EOF | |
| # Inject patches for GCC to compile without the redzone in libgcc for | |
| # x86_64 builds. | |
| if [[ -f gcc-x86_64-elf-redzone.patch ]]; then | |
| run_in_container <<-EOF | |
| echo "Injecting workaround to disable the red zone on x86_64-elf..." | |
| { | |
| echo "# Add libgcc multilib variant without red-zone requirement" | |
| echo "MULTILIB_OPTIONS += mno-red-zone" | |
| echo "MULTILIB_DIRNAMES += no-red-zone" | |
| } > src/gcc-${GCC_VERSION}/gcc/config/i386/t-x86_64-elf | |
| if [[ -f "src/gcc-${GCC_VERSION}/gcc/config.gcc.orig" ]]; then | |
| # Revert to original first if we detect we've already patched this file before. | |
| cp -v src/gcc-${GCC_VERSION}/gcc/config.gcc.orig src/gcc-${GCC_VERSION}/gcc/config.gcc | |
| fi | |
| patch -biN src/gcc-${GCC_VERSION}/gcc/config.gcc gcc-x86_64-elf-redzone.patch | |
| diff -Naru src/gcc-${GCC_VERSION}/gcc/config.gcc.orig src/gcc-${GCC_VERSION}/gcc/config.gcc || : | |
| EOF | |
| fi | |
| # Build gcc | |
| run_in_container <<-EOF | |
| echo "Building gcc..." | |
| mkdir -pv build/gcc | |
| export PREFIX="\$(pwd)/out" | |
| export TARGET=${arch} | |
| export PATH="\$PREFIX/bin:\$PATH" | |
| cd build/gcc | |
| echo "Compiling GCC." | |
| ../../src/gcc-${GCC_VERSION}/configure \ | |
| --target=${arch} \ | |
| --prefix="\${PREFIX}" \ | |
| --disable-multilib \ | |
| --disable-nls \ | |
| --enable-languages=c \ | |
| --without-headers \ | |
| --disable-hosted-libstdcxx \ | |
| --disable-werror | |
| make -j $(($(nproc) * 2)) all-gcc | |
| make -j $(($(nproc) * 2)) all-target-libgcc | |
| make install-gcc | |
| make install-target-libgcc | |
| EOF |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- src/gcc-14.2.0/gcc/config.gcc.orig 2025-02-09 15:48:04.601577221 +0000 | |
| +++ src/gcc-14.2.0/gcc/config.gcc 2025-02-09 15:48:04.603577227 +0000 | |
| @@ -1933,6 +1933,7 @@ | |
| tm_file="${tm_file} i386/unix.h i386/att.h elfos.h newlib-stdint.h i386/i386elf.h" | |
| ;; | |
| x86_64-*-elf*) | |
| + tmake_file="${tmake_file} i386/t-x86_64-elf" # include the new multilib configuration | |
| tm_file="${tm_file} i386/unix.h i386/att.h elfos.h newlib-stdint.h i386/i386elf.h i386/x86-64.h" | |
| ;; | |
| x86_64-*-rtems*) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment