Hosted GCC Cross-Compiler
Difficulty level |
---|
Advanced |
In this tutorial we create a cross-compiler that provides a hosted environment (user-space, libc, all that good stuff) as opposed to the initial GCC Cross-Compiler that only provides a freestanding environment. This is an advanced topic meant for established developers, newcomers should start with a freestanding cross-compiler. The -elf targets are incapable of having a user-space, so you need to make a OS Specific Toolchain before following this tutorial. You also need a C Library, perhaps a homemade one.
Introduction
This tutorial acts as a patch to the original GCC Cross-Compiler article by subtly changing the instructions. The caveats and instructions from it generally apply here, too. You are assumed to be an experienced reader that automatically remembers trivial matters such as adding the cross-compiler prefix bindir to the PATH before building the cross-compiler, or installing the dependencies of Binutils and GCC.
This article completes the work begun in OS Specific Toolchain by compiling it, thus implementing the next step in Porting GCC to your OS: Teaching GCC what your operating system is and making a cross-compiler for it.
You will be using a new target triplet, rather than i686-elf, we'll be using your custom i686-myos target. You will be supplying the Binutils and GCC source code yourself rather than using pure upstream releases.
Prerequisites
To correctly complete this tutorial, your libc must need particular minimum requirements. The libgcc will be built with libc support, meaning that your libc needs a few standard headers that define particular types, constants and functions. In particular, you need to supply:
- sys/types.h: This header is just required to exist and can be empty.
- errno.h: This header is just required to exist and can be empty.
- stdlib.h: abort(), free(), malloc().
- stdio.h: FILE, stderr, fflush(), fprintf().
- string.h: size_t, memcpy, memset(), strlen().
- time.h: This header is just required to exist and can be empty.
- unistd.h: This header is just required to exist and can be empty.
Additionally, the all-target-libgcc target also unconditionally builds the libgcov support library, which has these additional requirements:
- sys/types.h: pid_t.
- stdlib.h: atexit(), atoi(), getenv(), malloc(), calloc(), abs().
- stdio.h: size_t, SEEK_SET, fclose(), fopen(), fread(), fseek(), ftell(), fwrite(), setbuf(), vfprintf(), sprintf().
- string.h: strcpy(), strcat(), strchr().
- unistd.h: pid_t, intptr_t, fork(), execv(), execve(), execvp(), getpid().
Note how these are statically linked libraries and are not linked. It will do to simply add function prototypes with no implementation (or better yet, a stub implementation, or a real one) and no linker errors will happen unless the relevant parts of libgcc or libgcov are needed. Note how these are just the minimum requirements to get by without any implicit-function-declaration issues; the code is able to use more standard library features if available.
For the record and convenience, here are minimal headers that satisfies the requirements. (UPDATE: This repo is outdated, and GCC will still warn you about missing declarations. Just add them as you see them, it's only a few, and you can copy from your system headers.)
Sysroot Headers
Your previous i686-elf toolchain was built using the --without-headers configure option. This tells the compiler that there is no standard library and no headers. The libgcc is built with libc support as described above. This requires the standard library headers to be installed into your system root prior to the cross-compiler build.
cd $HOME/myos
mkdir -p sysroot
mkdir -p sysroot/usr/include
cp -RT libc/include sysroot/usr/include
cp -RT kernel/include sysroot/usr/include
Note: Not all cp(1) implementations support the -RT combination that conveniently merges the contents of one directory into another.
Binutils
cd $HOME/src
mkdir build-binutils
cd build-binutils
../myos-binutils/configure --target=i686-myos --prefix="$PREFIX" --with-sysroot=$HOME/myos/sysroot --disable-werror
make
make install
--with-sysroot= This option tells the compiler where your sysroot is. It is not used during the compilation process, but it is remembered and used when the linker searches for libraries.
GCC
cd $HOME/src
mkdir build-gcc
cd build-gcc
../myos-gcc/configure --target=i686-myos --prefix="$PREFIX" --with-sysroot=$HOME/myos/sysroot --enable-languages=c,c++
make all-gcc all-target-libgcc
make install-gcc install-target-libgcc
--with-sysroot= This option tells the compiler where your sysroot is. It is used during the libgcc build and is remembered and used when the compiler searches for headers and libraries.
libc
You can now compile your C Library normally using your fresh, custom cross-compiler.
libstdc++
The libstdc++ library was still not built above as it depends on libc as it needs to perform link tests to know about it. The GCC developers have poorly designed the libstdc++ package and it is tied to GCC and must be built as part of GCC, it can't be built on its own without tricks. If you saved your build-gcc directory from earlier, you can continue the process now (otherwise, it will build a full new cross-compiler for no good reason):
cd $HOME/src/build-gcc
make all-target-libstdc++-v3
make install-target-libstdc++-v3
Troubleshooting
In case you get an this error (occurs when using newlib):
./../../../gcc-11.3.0/libstdc++-v3/libsupc++/new_opa.cc:62:1: error: ‘void* aligned_alloc(std::size_t, std::size_t)’ was declared ‘extern’ and later ‘static’ [-fpermissive]
aligned_alloc (std::size_t al, std::size_t sz)
Open the file libstdc++-v3/libsupc++/new_opa.cc with an editor and remove the "static inline" before in the function prototype.
Conclusion
You now have a full i686-myos toolchain that provides a hosted environment producing executables for your user-space. Your program loader will load programs into the current process, set up an initial thread at the program entry point, and the process will perform system calls to cooperate with the kernel.
You can continue to polish your custom toolchain by teaching it more about your operating system and customizing its behavior to your needs. As your standard library and kernel support improves, you will become able to easily port third party software to your operating system by making third party software use your cross-compiler.