Breaking

Thursday, September 17, 2020

Cross-compilation
In a Linux system, if we write a simple "Hello world" program in c/c++ programming language, and compile it using gcc/g++ compiler, it will generate an executable binary file that can only be executed in Linux platform. We can't run that binary on the Windows platform. To make a Windows executable binary on the Linux system we need a different compiler. One thing to be noticed here is the host machine where we run the compiler (Linux) and the target machine for which we make the compilation (Windows) both are different platforms. And this is called cross-platform-compilation or cross-compilation.


This is typically used in the Embedded development systems. They required this cross-compiler in a faster platform like Linux or PowerPC to build the source code for a slower platform like ARM/AVR.

Toolchain
To make a cross-compiler we need a set of development tools that are linked together by specific stages called toolchain. 

A lot of prebuilt cross-platform toolchains are available. You may choose to use such packages instead of carrying out the procedure explained here. I suggest you read the book 'Building Embedded Linux Systems' 2nd edition to know the detailed procedure.

You can download the components of the GNU toolchain from the FSF's FTP site at ftp://ftp.gnu.org/gnu/ or any of its mirrors. The binutils package is in the binutils directory, the gcc package is in the gcc directory, and the glibc package is in the glibc directory.

With the appropriate tools in place, let us take a look at the procedure used to build the toolchain. These are the five main steps:

1. Kernel headers setup
2. Binary utility setup
3. Bootstrap compiler setup
4. C library setup
5. Full compiler setup

Before developing the toolchain we need to create new directories and set some environmental variables to make our configuration easy. Since we were logged in as non-root user, we should create new directories under the user folder to avoid any kind of permission error. 

The below command will create all directories required for this toolchain development.
$ mkdir toolchain toolchain/tools toolchain/kernel toolchain/build-tools toolchain/build-tools/build-binutils toolchain/build-tools/build-boot-gcc toolchain/build-tools/build-glibc toolchain/build-tools/build-gcc

Put below lines inside a script and source the script to set the environment variables.
export PROJECT=toolchain
export PRJROOT=/home/linuxbaya/demo-module/${PROJECT}
export TARGET=arm-linux
export PREFIX=${PRJROOT}/tools
export TARGET_PREFIX=${PREFIX}/${TARGET}
export PATH=${PREFIX}/bin:${PATH}
cd $PRJROOT

The TARGET variable defines the type of target for which your toolchain will be built. For PowerPC it would be powerpc-linux, for ARM it would be arm-linux, for SuperH4 it would be sh4-linux. The PREFIX variable provides the component configuration scripts with a pointer to the directory where we would like the target utilities to be installed. Conversely, TARGET_PREFIX is used for the installation of target-dependent header files and libraries.

Now move to the build-tool directory and download four packages binutils, gcc, and glibc from the FSF's FTP site at ftp://ftp.gnu.org/gnu/ or any of its mirrors. Everything is now almost ready for building the actual toolchain.

1. Kernel Headers Setup
As I said earlier, the setup of the kernel headers is the first step in building the toolchain. Select a kernel from the main kernel repository at http://www.kernel.org/ or any other mirror site, the first thing you need to do is download a copy of that kernel into the directory ${PRJROOT}/kernel.
$ tar xvzf linux-5.8.9.tar.gz
$ cd linux-5.8.9
$ make ARCH=arm INSTALL_HDR_PATH=headers/ headers_install

This will display a menu in your console where you will be able to select your kernel's configuration. Simply save and exit will create a .config file in your current working directory.  We can now create the include directory required for the toolchain and copy the kernel headers to it:
$ cp -r headers/include/asm /usr/include
$ cp -r include/linux/ /usr/include
$ cp -r include/asm-generic/ /usr/include

Kernel headers are required; usually during the compilation of glibc.

2. Binutils Setup
The binutils package includes the utilities most often used to manipulate binary object files. The two most important utilities within the package are the GNU assembler, as, and the linker, ld, and others are gasp, ar, nm, objcopy, objdump, ranlib, readelf, etc.

The first step in setting up the binutils package is to extract its source code from the archive we downloaded earlier:
$ cd ${PRJROOT}/build-tools
$ tar xvzf binutils-2.10.1.tar.gz
$ cd build-binutils
$ ../binutils-2.10.1/configure --target=$TARGET --prefix=${PREFIX}

With the Makefiles now ready, we can build the actual utilities:
$ make

With the package now built, we can install the binutils:
$ make install

The binutils have now been installed inside the directory pointed to by PREFIX. You can check to see that
$ ls ${PREFIX}/bin

3. Bootstrap Compiler Setup
Again, we start by extracting the gcc package from the archive we downloaded earlier:
$ cd ${PRJROOT}/build-tools
$ tar xvzf gcc-9.2.0.tar.gz

This will create a directory called gcc-9.2.0 with the package's content. Move to the directory and download the prerequisites.
$ cd gcc-9.2.0
$ ./contrib/download_prerequisites

We can now proceed to the configuration of the build in the directory we had prepared for the bootstrap compiler:
$ cd ../build-boot-gcc
$ ../gcc-9.2.0/configure --target=$TARGET --prefix=${PREFIX} \
> --without-headers --with-newlib --enable-languages=c

With the Makefiles ready, we can now build the compiler:
$ make all-gcc
$ make install-gcc

The bootstrap compiler is now installed alongside the binutils, and you can see it by relisting the content of ${PREFIX}/bin. The name of the compiler is called arm-linux-gcc in our example.

4. C Library Setup
In our cross-platform development toolchain, This package is the most delicate and lengthy package build. It is made up of many libraries.

As with the previous packages, we start by extracting the C library from the archive we downloaded earlier:
$ cd ${PRJROOT}/build-tools
$ tar xvzf glibc-2.32.tar.gz

This will create a directory called glibc-2.32 with the package's content. We can now proceed to preparing the build of the C library in the build-glibc directory:
$ cd build-glibc
$ CC=arm-linux-gcc ../glibc-2.32/configure --host=$TARGET \
> --prefix="/usr" --enable-add-ons \
> --with-headers=${TARGET_PREFIX}/include

Along with the other components of the C library, the link script has been installed in the ${TARGET_PREFIX}/lib directory. In its original form, libc.so looks like this:

/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a )

Modify it to:

/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
GROUP ( libc.so.6 libc_nonshared.a )

Before modify, I suggest you to make a copy of it.
$ cd ${TARGET_PREFIX}/lib
$ cp ./libc.so ./libc.so.orig

By modifying it now we are forcing the linker to use the libraries found within the same directory as the libc.so script, instead of the native ones found on your host.

5. Full Compiler Setup
We are now ready to install the full compiler for your target with both C and C++ support.
$ cd ${PRJROOT}/build-tools/build-gcc
$ ../gcc-9.2.0/configure --target=$TARGET --prefix=${PREFIX} \
> --enable-languages=c,c++

The options we use here have the same meaning as when building the bootstrap compiler. Notice that there are fewer options and that we now add support for C++ in addition to C. With the full compiler properly configured, we can now build it:
$ make all

Notice that we didn't use all-gcc as with the bootstrap compiler, but rather all. This will result in the build of all the rest of the components included with the gcc package, including the C++ runtime libraries. If you didn't properly configure the libc.so link script file as previously explained, the build will fail during the compilation of the runtime libraries.

With the full compiler now built, we can install it:
$ make install

This will install the full compiler over the bootstrap compiler we had previously installed.

Using the Toolchain
You now have a fully functional cross-development toolchain, which you can use very much as you would a native GNU toolchain, save for the additional target name prepended to every command you are used to. Instead of invoking gcc and objdump for your target, you will need to invoke arm-linux-gcc and arm-linux-objdump.

The following is a Makefile for the control daemon on a test module that provides a good example of the cross-development toolchain's use:

The first part of the Makefile specifies the names of the toolchain utilities we are using to build the program. The second part of the Makefile defines the build settings. CFLAGS provides the flags to be used during the build of any C file. If you wish to link your application statically, you need to add the -static option to LDFLAGS. This generates an executable that does not rely on any shared library. But given that the standard GNU C library is rather large, this will result in a very large binary. A simple program that uses printf( ) to print "Hello World!", for example, is less than 12 KB in size when linked dynamically and around 350 KB when linked statically and stripped.

The make rules themselves are very much the same ones you would find in a standard, native Makefile. I added the install rule to automate the install process.

close