Embedded Linux Qemu Labs
Embedded Linux Qemu Labs
Embedded Linux Qemu Labs
Practical Labs
https://bootlin.com
Ubuntu installation
Set up the Linux distribution: Ubuntu 20.04
Training setup
Download files and directories used in practical labs
Lab data are now available in an embedded-linux-qemu-labs directory in your home directory.
This directory contains directories and files used in the various practical labs. It will also be
used as working space, in particular to keep generated files separate when needed.
You are now ready to start the real practical labs!
More guidelines
Can be useful throughout any of the labs
• Read instructions and tips carefully. Lots of people make mistakes or waste time because
they missed an explanation or a guideline.
• Always read error messages carefully, in particular the first one which is issued. Some
people stumble on very simple errors just because they specified a wrong file path and
didn’t pay enough attention to the corresponding error message.
• Never stay stuck with a strange problem more than 5 minutes. Show your problem to
your colleagues or to the instructor.
• You should only use the root user for operations that require super-user privileges, such
as: mounting a file system, loading a kernel module, changing file ownership, configuring
1 This tool from Microsoft is Open Source! To try it on Ubuntu: sudo snap install code
the network. Most regular tasks (such as downloading, extracting sources, compiling...)
can be done as a regular user.
• If you ran commands from a root shell by mistake, your regular user may no longer be
able to handle the corresponding generated files. In this case, use the chown -R command
to give the new files back to your regular user.
Example: chown -R myuser.myuser linux/
Setup
Go to the $HOME/embedded-linux-qemu-labs/toolchain directory.
Getting Crosstool-ng
Let’s download the sources of Crosstool-ng, through its git source repository, and switch to a
commit that we have tested:
git clone https://github.com/crosstool-ng/crosstool-ng.git
cd crosstool-ng/
git checkout 5659366b
Crosstool-ng comes with a set of ready-made configuration files for various typical setups:
Crosstool-ng calls them samples. They can be listed by using ./ct-ng list-samples.
We will load the Cortex A9 sample. Load it with the ./ct-ng command.
Then, to refine the configuration, let’s run the menuconfig interface:
./ct-ng menuconfig
In Path and misc options:
• Change Maximum log level to see to DEBUG so that we can have more details on what
happened during the build in case something went wrong.
In Toolchain options:
• Set Tuple's vendor string to training.
• Set Tuple's alias to arm-linux. This way, we will be able to use the compiler as arm-
linux-gcc instead of arm-training-linux-uclibcgnueabihf-gcc, which is much longer
to type.
In Debug facilities, disable every option, except strace (with default settings). Some of
these options will be useful in a real toolchain, but in our labs, we will do debugging work with
another toolchain anyway. strace is an exception as we will use it earlier. Hence, not compiling
debugging features here will reduce toolchain building time.
Explore the different other available options by traveling through the menus and looking at the
help for some of the options. Don’t hesitate to ask your trainer for details on the available
options. However, remember that we tested the labs with the configuration described above.
You might waste time with unexpected issues if you customize the toolchain configuration.
Known issues
It is frequent that Crosstool-ng aborts because it can’t find a source archive on the Internet,
when such an archive has moved or has been replaced by more recent versions. New Crosstool-ng
versions ship with updated URLs, but in the meantime, you need work-arounds.
If this happens to you, what you can do is look for the source archive by yourself on the Internet,
and copy such an archive to the src directory in your home directory. Note that even source
archives compressed in a different way (for example, ending with .gz instead of .bz2) will be
fine too. Then, all you have to do is run ./ct-ng build again, and it will use the source archive
that you downloaded.
Cleaning up
Do this only if you have limited storage space. In case you made a mistake in the toolchain
configuration, you may need to run Crosstool-ng again. Keeping generated files would save a
significant amount of time.
To save about 8 GB of storage space, do a ./ct-ng clean in the Crosstool-NG source direc-
tory. This will remove the source code of the different toolchain components, as well as all the
generated files that are now useless since the toolchain has been installed in $HOME/x-tools.
Bootloader - U-Boot
Objectives: Compile and install the U-Boot bootloader, use basic U-
Boot commands, set up TFTP communication with the development
workstation.
Setup
Go to the $HOME/embedded-linux-qemu-labs/bootloader directory.
Install the qemu-system-arm package. In this lab and in the following ones, we will use a QEMU
emulated ARM Vexpress Cortex A9 board.
Configuring U-Boot
Download U-Boot v2020.04 and configure it to support the ARM Vexpress Cortex A9 board
(vexpress_ca9x4_defconfig)
Get an understanding of U-Boot’s configuration and compilation steps by reading the README
file, and specifically the Building the Software section.
Basically, you need to specify the cross-compiler prefix (the part before gcc in the cross-compiler
executable name):
export CROSS_COMPILE=arm-linux-
Now that you have a valid initial configuration, run make menuconfig to further edit your
bootloader features:
• In the Environment submenu, we will configure U-Boot so that it stores its environment
inside a file called uboot.env in a FAT filesystem on an MMC/SD card, as our emulated
machine won’t have flash storage:
– Unset Environment in flash memory (CONFIG_ENV_IS_IN_FLASH)
– Set Environment is in a FAT filesystem (CONFIG_ENV_IS_IN_FAT)
– Set Name of the block device for the environment (CONFIG_ENV_FAT_INTERFACE):
mmc
– Device and partition for where to store the environment in FAT (CONFIG_ENV_
FAT_DEVICE_AND_PART): 0:1
The above two settings correspond to the arguments of the fatload command.
• Also add support for the editenv command (CONFIG_CMD_EDITENV) that is not present in
the default configuration for our board Unfortunately, this command is currently broken.
In recent versions of U-Boot and for some boards, you will need to have the Device Tree compiler:
sudo apt install device-tree-compiler
Testing U-Boot
Still in U-Boot sources, test that U-Boot works:
qemu-system-arm -M vexpress-a9 -m 128M -nographic -kernel u-boot
• -M: emulated machine
• -m: amount of memory in the emulated machine
• -kernel: allows to load the binary directly in the emulated machine and run the machine
with it. This way, you don’t need a first stage bootloader. Of course, you don’t have this
with real hardware.
Press a key before the end of the timeout, to access the U-Boot prompt.
You can then type the help command, and explore the few commands available.
Note: to exit QEMU, run the below command in another terminal:
killall qemu-system-arm
SD card setup
We now need to add an SD card image to the QEMU virtual machine, in particular to get a
way to store U-Boot’s environment.
In later labs, we will also use such storage for other purposes (to store the kernel and device
tree, root filesystem and other filesystems).
The commands that we are going to use will be further explained during the Block filesystems
lectures.
First, using the dd command, create a 1 GB file filled with zeros, called codesd.img:
dd if=/dev/zero of=sd.img bs=1M count=1024
This will be used by QEMU as an SD card disk image
Now, let’s use the cfdisk command to create the partitions that we are going to use:
$ cfdisk sd.img
If cfdisk asks you to Select a label type, choose dos. This corresponds to traditional par-
titions tables that DOS/Windows would understand. gpt partition tables are needed for disks
bigger than 2 TB.
In the cfdisk interface, create three primary partitions, starting from the beginning, with the
following properties:
• One partition, 64MB big, with the FAT16 partition type. Mark this partition as bootable.
2 You can speed up the compiling by using the -jX option with make, where X is the number of parallel jobs
used for compiling. Twice the number of CPU cores is a good value.
• One partition, 8 MB big3 , that will be used for the root filesystem. Due to the geometry
of the device, the partition might be larger than 8 MB, but it does not matter. Keep the
Linux type for the partition.
• One partition, that fills the rest of the SD card image, that will be used for the data
filesystem. Here also, keep the Linux type for the partition.
Press Write when you are done.
We will now use the loop driver4 to emulate block devices from this image and its partitions:
sudo losetup -f --show --partscan sd.img
• -f: finds a free loop device
• --show: shows the loop device that it used
• --partscan: scans the loop device for partitions and creates additional /dev/loop<x>p<y>
block devices.
Last but not least, format the first partition as FAT16 with a boot label:
sudo mkfs.vfat -F 16 -n boot /dev/loop<x>p1
The other partitions will be formated later.
Now, you can release the loop device:
sudo losetup -d /dev/loop<x>
with the 8 GB SD cards that we use in our labs, 8 MB will be the smallest partition that cfdisk will allow you
to create.
4 Once again, this will be properly be explained during our Block filesystems lectures.
#!/bin/bash
exec /sbin/ifconfig $1 192.168.0.1
Of course, make this script executable:
sudo chmod +x /etc/qemu-myifup
As you can see, the host side will have the 192.168.0.1 IP address. We will use 192.168.0.100
for the target side. Of course, use a different IP address range if this conflicts with your local
network.
Then, you will need root privileges to run QEMU this time, because of the need to bring up the
network interface:
qemu-system-arm -M vexpress-a9 -m 128M -nographic \
-kernel u-boot-2020.04/u-boot \
-sd sd.img \
-net tap,script=/etc/qemu-myifup -net nic
Note the new net options:
• -net tap: creates a software network interface on the host side
• -net nic: adds a network device to the emulated machine
On the host machine, using the ifconfig command, check that there is now a tap0 network
interface with the expected IP address.
On the U-Boot command line, you will have to configure the environment variables for network-
ing:
setenv ipaddr 192.168.0.100
setenv serverip 192.168.0.1
saveenv
You can now test the connection to the host:
ping 192.168.0.1
It should finish by:
host 192.168.0.1 is alive
tftp setup
Install a tftp server on your host as explained in the slides.
Back in U-Boot, run bdinfo, which will allow you to find out that RAM starts at 0x60000000.
Therefore, we will use the 0x61000000 address to test tftp.
To test the TFTP connection, put a small text file in the directory exported through TFTP on
your development workstation. Then, from U-Boot, do:
tftp 0x61000000 textfile.txt
The tftp command should have downloaded the textfile.txt file from your development
workstation into the board’s memory at location 0x61000000.
You can verify that the download was successful by dumping the contents of the memory:
md 0x61000000
Rescue binary
If you have trouble generating binaries that work properly, or later make a mistake that causes
you to loose your bootloader binary, you will find a working version under data/ in the current
lab directory.
Kernel sources
Objective: Learn how to get the kernel sources and patch them.
Setup
Create the $HOME/embedded-linux-qemu-labs/kernel directory and go into it.
Apply patches
Download the patch files corresponding to the latest 5.6 stable release: a first patch to move
from 5.5 to 5.6 and if one exists, a second patch to move from 5.6 to 5.6.x.
Without uncompressing them to a separate file, apply the patches to the Linux source directory.
View one of the patch files with vi or gvim (if you prefer a graphical editor), to understand the
information carried by such a file. How are described added or removed files?
Rename the linux-5.5 directory to linux-5.6.<x>.
Kernel - Cross-compiling
Objective: Learn how to cross-compile a kernel for an ARM target
platform.
Setup
Go to the $HOME/embedded-linux-qemu-labs/kernel directory.
Kernel sources
We will re-use the kernel sources downloaded and patched in the previous lab.
Cross compiling
At this stage, you need to install the libssl-dev package to compile the kernel.
You’re now ready to cross-compile your kernel. Simply run:
make
and wait a while for the kernel to compile. Don’t forget to use make -j<n> if you have multiple
cores on your machine!
Look at the end of the kernel build output to see which file contains the kernel image. You can
also see the Device Tree .dtb files which got compiled. Find which .dtb file corresponds to your
board.
Copy the linux kernel image and DTB files to the TFTP server home directory.
Restart the board to make sure that booting the kernel is now automated.
Lab implementation
While (s)he develops a root filesystem for a device, a developer needs to make frequent changes
to the filesystem contents, like modifying scripts or adding newly compiled programs.
It isn’t practical at all to reflash the root filesystem on the target every time a change is made.
Fortunately, it is possible to set up networking between the development workstation and the
target. Then, workstation files can be accessed by the target through the network, using NFS.
Unless you test a boot sequence, you no longer need to reboot the target to test the impact of
script or application updates.
Setup
Go to the $HOME/embedded-linux-qemu-labs/tinysystem/ directory.
Kernel configuration
We will re-use the kernel sources from our previous lab, in $HOME/embedded-linux-qemu-labs/
kernel/.
In the kernel configuration built in the previous lab, verify that you have all options needed for
booting the system using a root filesystem mounted over NFS, and if necessary, enable them
and rebuild your kernel.
This happens because the kernel is trying to mount the devtmpfs filesystem in /dev/ in the root
filesystem. To address this, create a dev directory under nfsroot and reboot.
Now, the kernel should complain for the last time, saying that it can’t find an init application:
Kernel panic - not syncing: No working init found. Try passing init= option to kernel.
See Linux Documentation/init.txt for guidance.
Obviously, our root filesystem being mostly empty, there isn’t such an application yet. In the
next paragraph, you will add Busybox to your root filesystem and finally make it usable.
To configure BusyBox, we won’t be able to use make xconfig, which is currently broken for
BusyBox in Ubuntu 20.04, because it requires an old version of the Qt library.
Now, configure BusyBox with the configuration file provided in the data/ directory (remember
that the Busybox configuration file is .config in the Busybox sources).
If you don’t use the BusyBox configuration file that we provide, at least, make sure you build
BusyBox statically! Compiling Busybox statically in the first place makes it easy to set up the
system, because there are no dependencies on libraries. Later on, we will set up shared libraries
and recompile Busybox.
Build BusyBox using the toolchain that you used to build the kernel.
Going back to the BusyBox configuration interface specify the installation directory for Busy-
Box5 . It should be the path to your nfsroot directory.
Try to boot your new system on the board. You should now reach a command line prompt,
allowing you to execute the commands of your choice.
Virtual filesystems
Run the ps command. You can see that it complains that the /proc directory does not exist.
The ps command and other process-related commands use the proc virtual filesystem to get
their information from the kernel.
From the Linux command line in the target, create the proc, sys and etc directories in your
root filesystem.
Now mount the proc virtual filesystem. Now that /proc is available, test again the ps command.
Note that you can also now halt your target in a clean way with the halt command, thanks to
proc being mounted6 .
5 You will find this setting in Settings -> Install Options -> BusyBox installation prefix.
6 halt can find the list of mounted filesystems in /proc/mounts, and unmount each of them in a clean way
before shutting down.
Goals
After doing the A tiny embedded system lab, we are going to copy the filesystem contents to the
emulated SD card. The storage will be split into several partitions, and your QEMU emulated
board will be booted from this SD card, without using NFS anymore.
Setup
Throughout this lab, we will continue to use the root filesystem we have created in the $HOME/
embedded-linux-qemu-labs/tinysystem/nfsroot directory, which we will progressively adapt
to use block filesystems.
To illustrate how to use existing libraries and applications, we will extend the small root filesys-
tem built in the A tiny embedded system lab to add the ALSA libraries and tools and an audio
playback application using these libraries.
We’ll see that manually re-using existing libraries is quite tedious, so that more automated
procedures are necessary to make it easier. However, learning how to perform these operations
manually will significantly help you when you face issues with more automated tools.
Recompile your kernel with audio support. The options we want are: CONFIG_SOUND, CONFIG_SND,
CONFIG_SND_USB and CONFIG_SND_USB_AUDIO.
At this stage, the easiest solution to update your kernel is probably to get back to copying it
to RAM from tftp. Anyway, we will have to modify U-Boot environment variables, as we are
going to switch back to NFS booting anyway.
Make sure that your board still boots with this new kernel.
We’re going to integrate the alsa-utils and ogg123 executables. As most software components,
they in turn depend on other libraries, and these dependencies are different depending on the
configuration chosen for them. In our case, the dependency chain for alsa-utils is quite simple,
it only depends on the alsa-lib library.
The dependencies are a bit more complex for ogg123. It is part of vorbis-tools, that depend
on libao and libvorbis. libao in turn depends on alsa-lib, and libvorbis on libogg.
libao, alsa-utils and alsa-lib are here to abstract the use of ALSA, one of the Audio Subsys-
tems supported in Linux. vorbis-tools, libvorbis and libogg are used to handle the audio
files encoded using the Ogg codec, which is quite common.
Of course, all these libraries rely on the C library, which is not mentioned here, because it is
already part of the root filesystem built in the A tiny embedded system lab. You might wonder
how to figure out this dependency tree by yourself. Basically, there are several ways, that can
be combined:
• Read the help message of the configure script (by running ./configure --help).
To configure, compile and install all the components of our system, we’re going to start from
the bottom of the tree with alsa-lib, then continue with alsa-utils, libao, libogg, and libvorbis, to
finally compile vorbis-tools.
Preparation
For our cross-compilation work, we will need two separate spaces:
• A staging space in which we will directly install all the packages: non-stripped versions
of the libraries, headers, documentation and other files needed for the compilation. This
staging space can be quite big, but will not be used on our target, only for compiling
libraries or applications;
• A target space, in which we will only copy the required files from the staging space: binaries
and libraries, after stripping, configuration files needed at runtime, etc. This target space
will take a lot less space than the staging space, and it will contain only the files that are
really needed to make the system work on the target.
To sum up, the staging space will contain everything that’s needed for compilation, while the
target space will contain only what’s needed for execution.
For the target, we need a basic system with BusyBox and initialization scripts. We will re-use
the system built in the A tiny embedded system lab, so copy this system in the target directory:
cp -a $HOME/embedded-linux-qemu-labs/tinysystem/nfsroot/* target/
Note that for this lab, a lot of typing will be required. To save time typing, we advise you to
copy and paste commands from the electronic version of these instructions.
Testing
Make sure the target/ directory is exported by your NFS server to your board by modifying
/etc/exports and restarting your NFS server.
Make your board boot from this new directory through NFS.
alsa-lib
alsa-lib is a library supposed to handle the interaction with the ALSA subsystem. It is available
at https://alsa-project.org. Download version 1.2.2, and extract it in $HOME/embedded-
linux-qemu-labs/thirdparty/.
Tip: if the website for any of the source packages that we need to download in the next sections
is down, a great mirror that you can use is http://sources.buildroot.net/.
Back to alsa-lib sources, look at the configure script and see see that it has been generated
by autoconf (the header contains a sentence like Generated by GNU Autoconf 2.69). Most of
the time, autoconf comes with automake, that generates Makefiles from Makefile.am files. So
alsa-lib uses a rather common build system. Let’s try to configure and build it:
./configure
make
You can see that the files are getting compiled with gcc, which generates code for x86 and not
for the target platform. This is obviously not what we want, so we clean-up the object and tell
the configure script to use the ARM cross-compiler:
make clean
CC=arm-linux-gcc ./configure
Of course, the arm-linux-gcc cross-compiler must be in your PATH prior to running the configure
script. The CC environment variable is the classical name for specifying the compiler to use.
Quickly, you should get an error saying:
checking whether we are cross compiling... configure: error: in `.../thirdparty/alsa-lib-1.1.6':
configure: error: cannot run C compiled programs.
If you meant to cross compile, use `--host'.
See `config.log' for more details
If you look at the config.log file, you can see that the configure script compiles a binary with
the cross-compiler and then tries to run it on the development workstation. This is a rather
usual thing to do for a configure script, and that’s why it tests so early that it’s actually doable,
and bails out if not.
Obviously, it cannot work in our case, and the scripts exits. The job of the configure script
is to test the configuration of the system. To do so, it tries to compile and run a few sample
applications to test if this library is available, if this compiler option is supported, etc. But in
our case, running the test examples is definitely not possible.
We need to tell the configure script that we are cross-compiling, and this can be done using
the --build and --host options, as described in the help of the configure script:
System types:
--build=BUILD configure for building on BUILD [guessed]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
The --build option allows to specify on which system the package is built, while the --host
option allows to specify on which system the package will run. By default, the value of the
--build option is guessed and the value of --host is the same as the value of the --build
option. The value is guessed using the ./config.guess script, which on your system should
return i686-pc-linux-gnu. See https://www.gnu.org/software/autoconf/manual/html_node/
Specifying-Names.html for more details on these options.
So, let’s override the value of the --host option:
CC=arm-linux-gcc ./configure --host=arm-linux
The configure script should end properly now, and create a Makefile. Run the make command,
which should run just fine.
Look at the result of compiling in src/.libs: a set of object files and a set of libasound.so*
files.
The libasound.so* files are a dynamic version of the library. The shared library itself is
libasound.so.2.0.0, it has been generated by the following command line:
arm-linux-gcc -shared conf.o confmisc.o input.o output.o \
async.o error.o dlmisc.o socket.o shmarea.o \
userfile.o names.o -lm -ldl -lpthread -lrt \
-Wl,-soname -Wl,libasound.so.2 -o libasound.so.2.0.0
And creates the symbolic links libasound.so and libasound.so.2.
ln -s libasound.so.2.0.0 libasound.so.2
ln -s libasound.so.2.0.0 libasound.so
These symlinks are needed for two different reasons:
• libasound.so is used at compile time when you want to compile an application that is
dynamically linked against the library. To do so, you pass the -lLIBNAME option to the
compiler, which will look for a file named lib<LIBNAME>.so. In our case, the compilation
option is -lasound and the name of the library file is libasound.so. So, the libasound.so
symlink is needed at compile time;
• libasound.so.2 is needed because it is the SONAME of the library. SONAME stands for
Shared Object Name. It is the name of the library as it will be stored in applications linked
against this library. It means that at runtime, the dynamic loader will look for exactly
this name when looking for the shared library. So this symbolic link is needed at runtime.
To know what’s the SONAME of a library, you can use:
arm-linux-readelf -d libasound.so.2.0.0
and look at the (SONAME) line. You’ll also see that this library needs the C library, because of
the (NEEDED) line on libc.so.0.
The mechanism of SONAME allows to change the library without recompiling the applications
linked with this library. Let’s say that a security problem is found in the alsa-lib release that
provides libasound 2.0.0, and fixed in the next alsa-lib release, which will now provide libasound
2.0.1.
You can just recompile the library, install it on your target system, change the libasound.so.2
link so that it points to libasound.so.2.0.1 and restart your applications. And it will work,
because your applications don’t look specifically for libasound.so.2.0.0 but for the SONAME
libasound.so.2.
However, it also means that as a library developer, if you break the ABI of the library, you must
change the SONAME: change from libasound.so.2 to libasound.so.3.
Finally, the last step is to tell the configure script where the library is going to be installed.
Most configure scripts consider that the installation prefix is /usr/local/ (so that the library
is installed in /usr/local/lib, the headers in /usr/local/include, etc.). But in our system,
we simply want the libraries to be installed in the /usr prefix, so let’s tell the configure script
about this:
CC=arm-linux-gcc ./configure --host=arm-linux --disable-python --prefix=/usr
make
For this library, this option may not change anything to the resulting binaries, but for safety, it
is always recommended to make sure that the prefix matches where your library will be running
on the target system.
Do not confuse the prefix (where the application or library will be running on the target system)
from the location where the application or library will be installed on your host while building
the root filesystem.
For example, libasound will be installed in $HOME/embedded-linux-qemu-labs/thirdparty/
target/usr/lib/ because this is the directory where we are building the root filesystem, but
once our target system will be running, it will see libasound in /usr/lib.
The prefix corresponds to the path in the target system and never on the host. So, one should
never pass a prefix like $HOME/embedded-linux-qemu-labs/thirdparty/target/usr, otherwise
at runtime, the application or library may look for files inside this directory on the target
system, which obviously doesn’t exist! By default, most build systems will install the application
or library in the given prefix (/usr or /usr/local), but with most build systems (including
autotools), the installation prefix can be overridden, and be different from the configuration
prefix.
We now only have the installation process left to do.
First, let’s make the installation in the staging space:
make DESTDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging install
Now look at what has been installed by alsa-lib:
• Some configuration files in /usr/share/alsa
• The headers in /usr/include
• The shared library and its libtool (.la) file in /usr/lib
• A pkgconfig file in /usr/lib/pkgconfig. We’ll come back to these later
Finally, let’s install the library in the target space:
1. Create the target/usr/lib directory, it will contain the stripped version of the library
2. Copy the dynamic version of the library. Only libasound.so.2 and libasound.so.2.0.0
are needed, since libasound.so.2 is the SONAME of the library and libasound.so.2.0.0
is the real binary:
• cp -a staging/usr/lib/libasound.so.2* target/usr/lib
3. Strip the library:
• arm-linux-strip target/usr/lib/libasound.so.2.0.0
Alsa-utils
Download alsa-utils from the ALSA offical webpage. We tested the lab with version 1.2.2
Once uncompressed, we quickly discover that the alsa-utils build system is based on the autotools,
so we will work once again with a regular configure script.
As we’ve seen previously, we will have to provide the prefix and host options and the CC variable:
CC=arm-linux-gcc ./configure --host=arm-linux --prefix=/usr
Now, we should quiclky get an error in the execution of the configure script:
checking for libasound headers version >= 1.0.27... not present.
configure: error: Sufficiently new version of libasound not found.
Again, we can check in config.log what the configure script is trying to do:
configure:7146: checking for libasound headers version >= 1.0.27
configure:7208: arm-linux-gcc -c -g -O2 conftest.c >&5
conftest.c:12:10: fatal error: alsa/asoundlib.h: No such file or directory
Of course, since alsa-utils uses alsa-lib, it includes its header file! So we need to tell the C
compiler where the headers can be found: there are not in the default directory /usr/include/,
but in the /usr/include directory of our staging space. The help text of the configure script
says:
CPPFLAGS C/C++/Objective C preprocessor flags,
e.g. -I<include dir> if you have headers
in a nonstandard directory <include dir>
Let’s use it:
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
CC=arm-linux-gcc \
./configure --host=arm-linux --prefix=/usr
Now, it should stop a bit later, this time with the error:
checking for libasound headers version >= 1.0.27... found.
checking for snd_ctl_open in -lasound... no
configure: error: No linkable libasound was found.
The configure script tries to compile an application against libasound (as can be seen from
the -lasound option): alsa-utils uses alsa-lib, so the configure script wants to make sure this
library is already installed. Unfortunately, the ld linker doesn’t find it. So, let’s tell the linker
where to look for libraries using the -L option followed by the directory where our libraries
are (in staging/usr/lib). This -L option can be passed to the linker by using the LDFLAGS at
configure time, as told by the help text of the configure script:
LDFLAGS linker flags, e.g. -L<lib dir> if you have
libraries in a nonstandard directory <lib dir>
Let’s use this LDFLAGS variable:
LDFLAGS=-L$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib \
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
CC=arm-linux-gcc \
./configure --host=arm-linux --prefix=/usr
Once again, it should fail a bit further down the tests, this time complaining about a missing
curses helper header. curses or ncurses is a graphical framework to design UIs in the terminal.
This is only used by alsamixer, one of the tools provided by alsa-utils, that we are not going to
use. Hence, we can just disable the build of alsamixer.
Of course, if we wanted it, we would have had to build ncurses first, just like we built alsa-lib.
We will also need to disable support for xmlto that generates the documentation.
LDFLAGS=-L$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib \
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
CC=arm-linux-gcc \
./configure --host=arm-linux --prefix=/usr \
--disable-alsamixer --disable-xmlto
Then, run the compilation with make. Hopefully, it works!
Let’s now begin the installation process. Before really installing in the staging directory, let’s
install in a dummy directory, to see what’s going to be installed (this dummy directory will not
be used afterwards, it is only to verify what will be installed before polluting the staging space):
make DESTDIR=/tmp/alsa-utils/ install
The DESTDIR variable can be used with all Makefiles based on automake. It allows to override
the installation directory: instead of being installed in the configuration prefix directory, the
files will be installed in DESTDIR/configuration-prefix.
Now, let’s see what has been installed in /tmp/alsa-utils/ (run tree /tmp/alsa-utils):
/tmp/alsa-utils/
|-- lib
| |-- systemd
| | `-- system
| | |-- alsa-restore.service
| | |-- alsa-state.service
| | `-- sound.target.wants
| | |-- alsa-restore.service -> ../alsa-restore.service
| | `-- alsa-state.service -> ../alsa-state.service
| `-- udev
| `-- rules.d
| |-- 89-alsa-ucm.rules
| `-- 90-alsa-restore.rules
|-- usr
| |-- bin
| | |-- aconnect
| | |-- alsabat
| | |-- alsaloop
| | |-- alsatplg
| | |-- alsaucm
| | |-- amidi
| | |-- amixer
| | |-- aplay
| | |-- aplaymidi
| | |-- arecord -> aplay
| | |-- arecordmidi
| | |-- aseqdump
| | |-- aseqnet
| | |-- axfer
| | |-- iecset
| | `-- speaker-test
| |-- sbin
| | |-- alsabat-test.sh
| | |-- alsaconf
| | |-- alsactl
| | `-- alsa-info.sh
| `-- share
| |-- alsa
| | |-- init
| | | |-- 00main
| | | |-- ca0106
| | | |-- default
| | | |-- hda
| | | |-- help
| | | |-- info
| | | `-- test
| | `-- speaker-test
| | `-- sample_map.csv
| |-- man
| | |-- fr
| | | `-- man8
| | | `-- alsaconf.8
| | |-- man1
| | | |-- aconnect.1
| | | |-- alsabat.1
| | | |-- alsactl.1
| | | |-- alsa-info.sh.1
| | | |-- alsaloop.1
| | | |-- amidi.1
| | | |-- amixer.1
| | | |-- aplay.1
| | | |-- aplaymidi.1
| | | |-- arecord.1 -> aplay.1
| | | |-- arecordmidi.1
| | | |-- aseqdump.1
| | | |-- aseqnet.1
| | | |-- axfer.1
| | | |-- axfer-list.1
| | | |-- axfer-transfer.1
| | | |-- iecset.1
| | | `-- speaker-test.1
| | |-- man7
| | `-- man8
| | `-- alsaconf.8
| `-- sounds
| `-- alsa
| |-- Front_Center.wav
| |-- Front_Left.wav
| |-- Front_Right.wav
| |-- Noise.wav
| |-- Rear_Center.wav
| |-- Rear_Left.wav
| |-- Rear_Right.wav
| |-- Side_Left.wav
| `-- Side_Right.wav
`-- var
`-- lib
`-- alsa
24 directories, 63 files
So, we have:
• The manual pages in /usr/share/man/, explaining how to use the various tools
Then, let’s install only the necessary files in the target space, manually:
cd ..
cp -a staging/usr/bin/a* staging/usr/bin/speaker-test target/usr/bin/
cp -a staging/usr/sbin/alsa* target/usr/sbin
arm-linux-strip target/usr/bin/a*
arm-linux-strip target/usr/bin/speaker-test
arm-linux-strip target/usr/sbin/alsactl
mkdir -p target/usr/share/alsa/pcm
cp -a staging/usr/share/alsa/alsa.conf* target/usr/share/alsa/
cp -a staging/usr/share/alsa/cards target/usr/share/alsa/
cp -a staging/usr/share/alsa/pcm/default.conf target/usr/share/alsa/pcm/
Now test that all is working fine by running the speaker-test util on your board, with the
headset provided by your instructor plugged in. You may need to add the missing libraries from
the toolchain install directory.
Caution: don’t copy the dmix.conf file. speaker-test will tell you that it cannot find this file,
but it won’t work if you copy this file from the staging area.
The sound you get will be mainly noise (as what you would get by running speaker-test on
your PCs). This is a way to test all possible frequencies, but is not really meant for a human to
listen to. At least, sound output is showing some signs of life! It will get much better when we
play samples with ogg123.
libogg
Now, let’s work on libogg. Download the 1.3.4 version from https://xiph.org and extract it.
Configuring libogg is very similar to the configuration of the previous libraries:
CC=arm-linux-gcc ./configure --host=arm-linux --prefix=/usr
Of course, compile the library:
make
Installation to the staging space can be done using the classical DESTDIR mechanism:
make DESTDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging/ install
And finally, only install manually in the target space the files needed at runtime:
cd ..
cp -a staging/usr/lib/libogg.so.0* target/usr/lib/
arm-linux-strip target/usr/lib/libogg.so.0.8.4
Done with libogg!
libvorbis
Libvorbis is the next step. Grab the 1.3.6 version from https://xiph.org and uncompress it.
Once again, the libvorbis build system is a nice example of what can be done with a good usage
of the autotools. Cross-compiling libvorbis is very easy, and almost identical to what we’ve seen
with alsa-utils. First, the configure step:
CC=arm-linux-gcc \
./configure --host=arm-linux --prefix=/usr
It will fail with:
configure: error: Ogg >= 1.0 required !
By running ./configure --help, you will find the --with-ogg-libraries and --with-ogg-
includes options. Use these:
CC=arm-linux-gcc ./configure --host=arm-linux --prefix=/usr \
--with-ogg-includes=$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
--with-ogg-libraries=$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib
Then, compile the library:
make
Install it in the staging space:
make DESTDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging/ install
And install only the required files in the target space:
cd ..
cp -a staging/usr/lib/libvorbis.so.0* target/usr/lib/
arm-linux-strip target/usr/lib/libvorbis.so.0.4.8
cp -a staging/usr/lib/libvorbisfile.so.3* target/usr/lib/
arm-linux-strip target/usr/lib/libvorbisfile.so.3.3.7
libao
Now, let’s work on libao. Download the 1.2.0 version from https://xiph.org and extract it.
Configuring libao is once again fairly easy, and similar to every sane autotools based build
system:
LDFLAGS=-L$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib \
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
CC=arm-linux-gcc ./configure --host=arm-linux \
--prefix=/usr
Of course, compile the library:
make
Installation to the staging space can be done using the classical DESTDIR mechanism:
make DESTDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging/ install
And finally, install manually the only needed files at runtime in the target space:
cd ..
cp -a staging/usr/lib/libao.so.4* target/usr/lib/
arm-linux-strip target/usr/lib/libao.so.4.1.0
We will also need the alsa plugin that is loaded dynamically by libao at startup:
mkdir -p target/usr/lib/ao/plugins-4/
cp -a staging/usr/lib/ao/plugins-4/libalsa.so target/usr/lib/ao/plugins-4/
Done with libao!
vorbis-tools
Finally, thanks to all the libraries we compiled previously, all the dependencies are ready. We
can now build the vorbis tools themselves. Download the 1.4.0 version from the official website,
at https://xiph.org/. As usual, extract the tarball.
Before starting the configuration, let’s have a look at the available options by running ./
configure --help. Many options are available. We see that we can, in addition to the usual
autotools configuration options:
• Enable/Disable the various tools that are going to be built: ogg123, oggdec, oggenc, etc.
• Enable or disable support for various other codecs: FLAC, Speex, etc.
• Enable or disable the use of various libraries that can optionally be used by the vorbis
tools
So, let’s begin with our usual configure line:
LDFLAGS=-L$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib \
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
CC=arm-linux-gcc \
./configure --host=arm-linux --prefix=/usr
At the end, you should see the following warning:
configure: WARNING: Prerequisites for ogg123 not met, ogg123 will be skipped.
Please ensure that you have POSIX threads, libao, and (optionally) libcurl
libraries and headers present if you would like to build ogg123.
Which is unfortunate, since we precisely want ogg123.
If you look back at the script output, you should see that at some point that it tests for libao
and fails to find it:
checking for AO... no
configure: WARNING: libao too old; >= 1.0.0 required
If you look into the config.log file now, you should find something like:
configure:22343: checking for AO
configure:22351: $PKG_CONFIG --exists --print-errors "ao >= 1.0.0"
Package ao was not found in the pkg-config search path.
Perhaps you should add the directory containing `ao.pc'
to the PKG_CONFIG_PATH environment variable
No package 'ao' found
In this case, the configure script uses the pkg-config system to get the configuration parameters
to link the library against libao. By default, pkg-config looks in /usr/lib/pkgconfig/ for .pc
files, and because the libao-dev package is probably not installed in your system the configure
script will not find libao library that we just compiled.
It would have been worse if we had the package installed, because it would have detected and
used our host package to compile libao, which, since we’re cross-compiling, is a pretty bad thing
to do.
This is one of the biggest issue with cross-compilation: mixing host and target libraries, because
build systems have a tendency to look for libraries in the default paths.
So, now, we must tell pkg-config to look inside the /usr/lib/pkgconfig/ directory of our staging
space. This is done through the PKG_CONFIG_LIBDIR environment variable, as explained in the
manual page of pkg-config.
Moreover, the .pc files contain references to paths. For example, in $HOME/embedded-linux-
qemu-labs/thirdparty/staging/usr/lib/pkgconfig/ao.pc, we can see:
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
[...]
Libs: -L${libdir} -lao
Cflags: -I${includedir}
So we must tell pkg-config that these paths are not absolute, but relative to our staging space.
This can be done using the PKG_CONFIG_SYSROOT_DIR environment variable.
Then, let’s run the configuration of the vorbis-tools again, passing the PKG_CONFIG_LIBDIR and
PKG_CONFIG_SYSROOT_DIR environment variables:
LDFLAGS=-L$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib \
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
PKG_CONFIG_LIBDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib/pkgconfig \
PKG_CONFIG_SYSROOT_DIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging \
CC=arm-linux-gcc \
Now, the configure script should end properly, we can now start the compilation:
make
It should fail with the following cryptic message:
make[2]: Entering directory '/home/tux/embedded-linux-qemu-labs/thirdparty/vorbis-tools-1.4.0/ogg123'
if arm-linux-gcc -DSYSCONFDIR=\"/usr/etc\" -DLOCALEDIR=\"/usr/share/locale\" -DHAVE_CONFIG_H -I. -I. -I.. -I/usr/include -I../
then mv -f ".deps/audio.Tpo" ".deps/audio.Po"; else rm -f ".deps/audio.Tpo"; exit 1; fi
In file included from audio.c:22:
/usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: No such file or directory
You can notice that /usr/include is added to the include paths. Again, this is not what we
want because it contains includes for the host, not the target. It is coming from the autodetected
value for CURL_CFLAGS.
Add the --without-curl option to the configure invocation, restart the compilation.
The compilation may then fail with an error related to libm. While the code uses the function
from this library, the generated Makefile doesn’t give the right command line argument in order
to link against the libm.
If you look at the configure help, you can see
LIBS libraries to pass to the linker, e.g. -l<library>
And this is exactly what we are supposed to use to add new linker flags.
Add this to the configure command line to get
LDFLAGS=-L$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib \
CPPFLAGS=-I$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/include \
PKG_CONFIG_LIBDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging/usr/lib/pkgconfig \
PKG_CONFIG_SYSROOT_DIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging \
LIBS=-lm \
CC=arm-linux-gcc \
./configure --host=arm-linux --prefix=/usr --without-curl
Finally, it builds!
Now, install the vorbis-tools to the staging space using:
make DESTDIR=$HOME/embedded-linux-qemu-labs/thirdparty/staging/ install
And then install them in the target space:
cd ..
cp -a staging/usr/bin/ogg* target/usr/bin
arm-linux-strip target/usr/bin/ogg*
You can now test that everything works! Run ogg123 on the sample file found in thirdparty/
data.
There should still be one missing C library object. Copy it, and you should get: +
ERROR: Failed to load plugin /usr/lib/ao/plugins-4/libalsa.so => dlopen() failed
=== Could not load default driver and no driver specified in config file. Exiting.
This error message is unfortunately not sufficient to figure out what’s going wrong. It’s a good
opportunity to use the strace utility (covered in upcoming lectures) to get more details about
what’s going on. To do so, you can used the one built by Crosstool-ng inside the toolchain
target/usr/bin directory.
You can now run ogg123 through strace:
strace ogg123 /sample.ogg
You can see that the command fails to open the ld-uClibc.so.1 file:
open("/lib/ld-uClibc.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/ld-uClibc.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/ld-uClibc.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/X11R6/lib/ld-uClibc.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/tux/embedded-linux-qemu-labs/thirdparty/staging/usr/lib/ld-uClibc.so.1", O_RDONLY) = -1 ENOENT
write(2, "ERROR: Failed to load plugin ", 29ERROR: Failed to load plugin ) = 29
write(2, "/usr/lib/ao/plugins-4/libalsa.so", 32/usr/lib/ao/plugins-4/libalsa.so) = 32
write(2, " => dlopen() failed\n", 20 => dlopen() failed
Now, look for ld-uClibc.so.1 in the toolchain. You can see that both ld-uClibc.so.1 and
ld-uClibc.so.0 are symbolic links to the same file. So, create the missing link under target/lib
and run ogg123 again.
Everything should work fine now. Enjoy the sound sample!
Known issue: the sample.ogg sample will play in a weird way in QEMU (too slow, apparently,
we haven’t found any solution yet). To have a correct result instead, use the aplay command
from alsa-utils, and play the sample.wav file provided together with sample.ogg.
To finish this lab completely, and to be consistent with what we’ve done before, let’s strip the
libraries in target/lib:
arm-linux-strip target/lib/*
Setup
Create the $HOME/embedded-linux-qemu-labs/buildroot directory and go into it.
Configure Buildroot
In our case, we would like to:
∗ Select vorbis-tools
• Filesystem images
– Select tar the root filesystem
Exit the menuconfig interface. Your configuration has now been saved to the .config file.
Known issue: as in the previous lab, the sample.ogg sample may play in a weird way in QEMU.
To have a correct result instead, use aplay to play the sample.wav file provided together with
sample.ogg.
Going further
• Add dropbear (SSH server and client) to the list of packages built by Buildroot and log to
your target system using an ssh client on your development workstation. Hint: you will
have to set a non-empty password for the root account on your target for this to work.
• Add a new package in Buildroot for the GNU Gtypist game. Read the Buildroot documen-
tation to see how to add a new package. Finally, add this package to your target system,
compile it and run it. The newest versions require a library that is not fully supported by
Buildroot, so you’d better stick with the latest version in the 2.8 series.
Application development
Objective: Compile and run your own ncurses application on the
target.
Setup
Go to the $HOME/embedded-linux-qemu-labs/appdev directory.
9 Again, output/host/usr/bin has a special pkg-config that automatically knows where to look, so it already
knows the right paths to find .pc files and their sysroot.
Setup
Go to the $HOME/embedded-linux-qemu-labs/debugging directory. Create an nfsroot directory.
Debugging setup
Because of issues in gdb and ltrace in the uClibc version that we are using in our toolchain, we
will use a different toolchain in this lab, based on glibc.
As glibc has more complete features than lighter libraries, it looks like a good idea to do your
application debugging work with a glibc toolchain first, and then switch to lighter libraries once
your application and software stack is production ready.
Extract the Buildroot 2020.02.<n> sources into the current directory.
Then, in the menuconfig interface, configure the target architecture as done previously but
configure the toolchain and target packages differently:
• In Toolchain:
– Toolchain type: External toolchain
– Toolchain: Custom Toolchain
– Toolchain origin: Toolchain to be downloaded and installed
– Toolchain URL: https://toolchains.bootlin.com/downloads/releases/toolchains/
armv7-eabihf/tarballs/armv7-eabihf--glibc--bleeding-edge-2018.07-3.tar.bz2
You can easily choose such a toolchain on https://toolchains.bootlin.com by se-
lecting the architecture, the C library and the compiler version you need. While you
can try with other versions, the above toolchain is known to make this lab work.
– External toolchain gcc version: 8.x
– External toolchain kernel headers series: 4.14.x
– External toochain C library: glibc/eglibc
– Select Toolchain has SSP support?
– Select Toolchain has RPC support?
– Select Toolchain has C++ support?
– Select Copy gdb server to the Target
• Target packages
– Debugging, profiling and benchmark
∗ Select ltrace
∗ Select strace
Now, build your root filesystem.
Go back to the $HOME/embedded-linux-qemu-labs/debugging directory and extract the buildroot-
2020.02.<n>/output/images/rootfs.tar archive in the nfsroot directory.
Add this directory to the /etc/exports file and restart nfs-kernel-server.
Boot your ARM board over NFS on this new filesystem, using the same kernel as before.
Using strace
Now, go to the $HOME/embedded-linux-qemu-labs/debugging directory.
strace allows to trace all the system calls made by a process: opening, reading and writing files,
starting other processes, accessing time, etc. When something goes wrong in your application,
strace is an invaluable tool to see what it actually does, even when you don’t have the source
code.
Update the PATH:
export PATH=$HOME/embedded-linux-qemu-labs/debugging/buildroot-2020.02.<n>/output/host/bin:$PATH
With your cross-compiling toolchain compile the data/vista-emulator.c program, strip it with
arm-linux-strip, and copy the resulting binary to the /root directory of the root filesystem.
Back to target system, try to run the /root/vista-emulator program. It should hang indefi-
nitely!
Interrupt this program by hitting [Ctrl] [C].
Now, running this program again through the strace command and understand why it hangs.
You can guess it without reading the source code!
Now add what the program was waiting for, and now see your program proceed to another bug,
failing with a segmentation fault.
Using ltrace
Now run the program through ltrace.
Now you should see what the program does: it tries to consume as much system memory as it
can!
Also run the program through ltrace -c, to see what function call statistics this utility can
provide.
It’s also interesting to run the program again with strace. You will see that memory allocations
translate into mmap() system calls. That’s how you can recognize them when you’re using strace.
Using gdbserver
We are now going to use gdbserver to understand why the program segfaults.
Compile vista-emulator.c again with the -g option to include debugging symbols. This time,
just keep it on your workstation, as you already have the version without debugging symbols
on your target.
Then, on the target side, run vista-emulator under gdbserver. gdbserver will listen on a TCP
port for a connection from gdb, and will control the execution of vista-emulator according to
the gdb commands:
gdbserver localhost:2345 vista-emulator
On the host side, run arm-linux-gdb (also found in your toolchain):
arm-linux-gdb vista-emulator
You can also start the debugger through the ddd interface:
ddd --debugger arm-linux-gdb vista-emulator
gdb starts and loads the debugging information from the vista-emulator binary that has been
compiled with -g.
Then, we need to tell where to find our libraries, since they are not present in the default /lib
and /usr/lib directories on your workstation. This is done by setting the gdb sysroot variable
(on one line):
(gdb) set sysroot /home/<user>/embedded-linux-qemu-labs/debugging/
buildroot-2020.02.<n>/output/staging
Of course, replace <user> by your actual user name.
And tell gdb to connect to the remote system:
(gdb) target remote <target-ip-address>:2345
Then, use gdb as usual to set breakpoints, look at the source code, run the application step by
step, etc. Graphical versions of gdb, such as ddd can also be used in the same way. In our case,
we’ll just start the program and wait for it to hit the segmentation fault:
(gdb) continue
You could then ask for a backtrace to see where this happened:
(gdb) backtrace
This will tell you that the segmentation fault occurred in a function of the C library, called by
our program. This should help you in finding the bug in our application.
What to remember
During this lab, we learned that...
• It’s easy to study the behavior of programs and diagnose issues without even having the
source code, thanks to strace.
• You can leave a small gdbserver program (about 300 KB) on your target that allows to
debug target applications, using a standard gdb debugger on the development host.
• It is fine to strip applications and binaries on the target machine, as long as the programs
and libraries with debugging symbols are available on the development host.