Autotools and Cross Compilation
When working with Linux based embedded systems it is essential to cross compile applications to the required target architecture. It is necessary to understand how packages are built, and how the build can be customized to build applications for Linux based embedded systems. This article will be a starting point for discussing about embedded Linux build systems, like Yocto.
The Vagrant based VM environment for this tutorial can be obtained using the following procedure. The "Try Out" sections can be executed within this Vagrant environment.
vagrant upin the directory where the
Vagrantfileis stored to setup the Vagrant box.
SSH to the Vagrant environment using
Many Free Software packages are written in C, and are distributed as source pacakges. These need to be downloaded and built as required by the user. In this section, we will look at build related problems and the solution provided by GNU Autotools, which is used in many Free Software packages.
One of the features provided by Autotools, is the ability to detect the OS features, and allow the programmer to take appropriate action if required. Before we look into this capability of Autotools, let’s understand what are the different OS variants, their differences, and how programmer’s would have to deal with these differences.
There are many variant of Unix-like operating systems: Linux, Solaris, FreeBSD, NetBSD, etc. These variants provide the same basic set of commands and system calls. But these commands and system calls differ in minor ways. It is generally hard to write a non-trivial program that compiles and runs on all variants. Some of these OS variantions are listed below:
Library function availability: Some OS variants do not have certain functions implemented, like
System Call availability: Some variants do not implement certain systems calls, like
System Call alternatives: Some system calls have alternate implementation in each OS variant, like
If we have to implement a program, that has to support multiple Operating Systems, then a local implementation or reduced mode of operation can be implemented based on the OS. The OS can be identified using OS specific macros.
Linux and Linux-derived
Darwin (Mac OS X and iOS)
The following example shows how a local implementation of a library function can be provided based on the OS macro, for a fictional OS called Tiny BSD, that does not have an implementation of memmove.
This way only if the OS is Tiny BSD, the memmove will be defined. But
what if there are tiny variants of other OS, that also need
memmove. The implementation would be as shown below.
#if defined(__TINYBSD__) || defined(__NANOLINUX__) || defined(__USOLARIS__)
The problem with this approach is that, every time a new Operating System without a feature is identified the source code has to be modified to add that OS to the list. If any of these OS, in a future version add support to the feature, then based on the OS version, the alternative action needs to be taken. And hence this solution is not future proof, and will also make the code a mess of preprocessor checks for OS and their versions.
Feature Macros and Feature Probing
Enter feature macros. Instead of checking what OS, that the
application is being built for, we write a feature probing shell
script that tests for the availability of various functions required
by the program, and generate a
config.h file with feature macros for
each of those features.
/* #define HAVE_SELECT */
These macros can then be used for taking alternate actions in the program.
This approach is future proof. Any future OS that does not support
memmove() is already supported, because the feature probing script
would detect the absence of
memmove() and would define macros in the
But how does the feature probing script identify the presence of a
function? The feature probing script generates a tiny program
containing an invocation of a function, compiles it and checks for
compilation errors. If a compilation error occurs then the shell
script concludes that the function is not present. An example C code
to test for
memmove is shown below.
char a, b;
memmove(a, b, 2);
The same technique can be used to identify availability of system calls, availability of types, presence of a member within a structure, presence of a header file, etc.
Feature Probing with Autotools
Feature probing is a very flexible and extensible technique for
identifying features in the system. Autotools provides a way of
creating the feature probing script, which can check for specific
features. The feature probing script generated by Autotools is called
configure script when executed, creates
config.h with the
results of the feature probing. The feature probing can also check for
the presence of libraries. If feature is specified as optional,
configure will continue with macro indicating feature is not
available. If a feature is required, and is not preset in the system,
configure will halt indicating feature is missing. Let’s try this
out with the Bash software package, the default shell in GNU/Linux
Create a workspace first, and set the variable
$WORK to the
workspace location. We will also create a separate downloads folder
and where all downloaded packages will be stored.
mkdir -p ~/workspace/dl
mkdir -p ~/workspace/autotools
Download Bash from http://ftp.gnu.org/gnu/bash/bash-4.3.tar.gz and extract it.
wget -c http://ftp.gnu.org/gnu/bash/bash-4.3.tar.gz
tar -x -f $DL/bash-4.3.tar.gz
bash-4.3 folder and run
A file called
config.h is created, check the contents of
config.h. Does it have macro definitions like
This is basically how Autotools allows the OS level feature differences to be identified and the software package configuration for the build step to be generated. This configuration step is based on OS features is completely automatic, but some configuration might be based on user’s preferences.
The manual configuration is an essential step in many large programs, like for example the Linux Kernel. The Linux Kernel has an elaborate menu based interface to disable / enable features required. Many application programs might also need something similar. Examples of features could be, to buid with / without GUI, select between alternate GUI libraries like Qt and GTK+.
Autotools provides a minimialist interface through which such
configuration can be specified. These features can be specified as an
option to the
configure script. Example of options would be
--with-qt=no, etc. These are
config.h and other build files generated by the
Let’s try this out in the
bash package. Run the configure command as
configure completes, check if the
HISTORY macro defined in
config.h. Now try configuring with history enabled, and check for
In this section, we have described why autotools is required, how autotools performs automatic feature probing and configuration, and how manual configuration can be specified. In the next section we will look at cross-compilation of software packages using autotools.
Cross Compilation with Autotools
Programs that use Autotools can be built using the following sequence of commands.
As seen in the previous section, the configure script does automatic feature probing and selection and manual feature selection can be done by passing options to the configure script.
When a software package is built, it is built to be executed in the current system. But if the program has to be executed in a different system, then this needs to be specified as part of the configuration.
Autotools refers to the system in which a program is built as the "build" system and the system in which the program is to be executed as the "host" system. This can be confusing to new users, since we are accustomed to referring to the system where the program is to be executed as the "target" system. But the name "target" is reserved in autotools for a different purpose. This is only relavent while building a program like a compiler. When building a compiler, there are three different systems that are involved:
the system where the compiler is to be executed.
the system where the compiler is being built.
the system for which compiler will be creating executable for.
For a compiler, each one of these can be a different system! This explains why the name "target" is not used to refer to the system when program is to be executed.
It is required to specify the "host" and the "build" system type when
performing a cross-compilation. The system type is specified using a
canonical name, that has the format
of canonical names are listed below:
for ARM systems running GNU/Linux
for PC systems running GNU/Linux
for SPARC systems running Solaris
for Apple systems running Mac OS X
Cross Compiling Packages
The canonical name also happens to be the prefix for the
cross-compiler. So if the host system is specified as
arm-none-linux-gnu, then autotools will use the compiler
arm-none-linux-gnu-gcc. And hence to cross-compile to ARM based
Linux systems the following command sequence can be used.
The cross compiler is installed as part of the package
./configure --host=arm-linux-gnueabi --build=i686-pc-linux-gnu
Let’s try this out with Bash.
./configure --host=arm-linux-gnueabi --build=i686-pc-linux-gnu
Check the architecture of the binary file using the
file command, to
confirm that the file has infact been compiled for the ARM
Compiling a package for a different architecture is only one part of the problem. There are other things that needs to be taken care of, when building a packge for use on an embedded Linux system. One of this, is the location in which the program files will be installed in the root filesystem.
The entire filesystem in a desktop is under the control of the package
rpm, except for
/usr/local. Any file
/usr/local is likely to be overwritten by the
package manager. And hence, when packages manually compiled, are by
default installed in
/usr/local. And the program expects to find its
data files under
/usr/local. When the program wants to access its
data file, it does as shown below:
fd = open("/usr/local/share/vlc/icon.png");
The program can be built to reside under
/usr, using the
option to the
configure script, as shown below.
The configure script creates a
PREFIX macro definition in
config.h. All static data files are accessed relative to
fd = open(PREFIX "/share/vlc/icon.png");
When building for use in an embedded Linux system, we would like to
/usr as prefix.
Let’s check the reference to data files within the bash binary using the following command.
strings bash | grep '/usr/local/share'
Reconfigure bash with
/usr prefix, and check strings for
./configure --host=arm-linux-gnueabi --build=i686-pc-linux-gnu --prefix=/usr
strings bash | grep '/usr/share'
After the program has been cross-compiled it needs to be installed
into the root filesystem directory of the target system. Generally the
installation is done using the
make install command. But this
command will end up installing the program into the filesystem of the
build system. Instead, we can specify a location where the target’s
root filesystem is being constructed, as shown below.
make install DESTDIR=/path/to/root
make install (as non-root), to verify that the program is being
is installed in the host’s fielsystem. Run
make install with
DESTDIR set as show below, and verify the contents of
make install DESTDIR=$WORK/rootfs
This article has shown some of the reasons for using the autotools framework, and how to cross-compile software packages that use the autotools framework. In the next article, we will show how to build a basic root filesystem from scratch.
Resources and Further Reading
The OS macros for each operating system was obtained from http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system
Autotools Mythbuster: https://autotools.io/
The GNU Autoobook Autoconf, Automake, and Libtool. URL: http://sources.redhat.com/autobook/autobook/autobook_13.html