1
Cross Compiling and Testing
Stephen M. Webb edited this page 2023-05-18 13:45:35 -04:00

Building for a Non-native Target

libunwind supports a broad range of target hardware and operating systems. It isn't practical to build libunwind self-hosted for every combination of target hardware and operating system, especially for embedded systems like Android and QNX or unhosted Sh or RISC-V systems.

It's possible to cross-build libunwind for an entirely different target operating system and instruction set architecture (ISA). This is done using the --build and --host arguments passed to configureas well as the settings for CC (the C compiler) and various other variables.

Testing using QEMU

QEMU is a tool that allows (on some development hosts) the emulation of a CPU of a different ISA (instruction set architecture). For example, running a binary built for aarch64 Linux on an x86_64 Linux host. QEMU provides two types of emulation: a full machine emulation (which requires booting up a target kernel and runtime) and static emulation (which just translates the instructions of a foreign binary on your current system). Using full machine emulation is beyond the scope of this article. QEMU for static emulation of a number of supported architectures can be installed on Ubuntu using the command sudo apt install qemu-static.

A test driver script is shipped with the source that is specialized for running the unit tests under QEMU. It needs to be specified at configuration time using the --with-testdriver option.

For example, building for an aarch64 Linux target on an x86_64 Linux host, take the following steps.

  1. Create a build directory (never build in the source directory) and make that your current working directory. In this case, we're also specifying to use a GCC 10 cross compiler (the CC argument) and a particular variant of the aarch64 ISA.
$ mkdir build-linux-aarch64
$ cd build-linux-aarch64
  1. Configure for cross-building with the QEMU test driver script.
$ ../configure --build=x86_64-linux-gnu --host=aarch64-linux-gnu --with-testdriver=$(pwd)/../scripts/qemu-test-driver --enable-debug CFLAGS="-march=armv8.2-a+sve -O2" CC=aarch64-linux-gnu-gcc-10
  1. Build libunwind. In this case, using 8 builder threads.
$ make -j8

Running make check

Running the entire test suite under emulation requires some extra flags passed to make.

$ make -j8 check LOG_DRIVER_FLAGS="--qemu-arch aarch64" LDFLAGS="-L/usr/aarch64-linux-gnu/lib -static" QEMU_LD_PREFIX=/usr/aarch64-linux-gnu CFLAGS="-O2"

In this case, relevant flags are LOG_DRIVER_FLAGS which tells qemu-test-driver what architecture to use, LDFLAGS which actually tells the linker where to find the arch-specific libraries, QEMU_LD_PREFIX which tells QEMU where to find its runtime dynamic libraries (in particular the dynamic loader), and -static because it's a pain to test shared libraries under QEMU.

This will run all the tests listed under the TESTS target in tests/Makefile.am, writing a .sum and a .trs file for each (see the GNU Automake documentation for information on these files.

Running an individual test

Sometimes you need to just run one single test. In that case you can invoke the test directly under QEMU using the following command.

$ UNW_DEBUG_LEVEL=7 QEMU_LD_PREFIX=/usr/aarch64-linux-gnu qemu-aarch64-static ./test-ptrace -v -s ls /

In this case, UNW_DEBUG_LEVEL is being set to glean internal debugging information from libunwind (because it was built with --enable-debug), QEMU_LD_PREFIX tells QEMU where to find its dynamic loader, and test-ptrace is the name of the test. Everything after test-ptrace is commond-line arguments to that program.

Debugging under QEMU using gdb

Sometimes you need to investigate a crash in a program run under QEMU. A good tool for this on a GNU/Linux development host is multiarch gdb. On Ubuntu you can install gdb for a number of supported architectures with the command sudo apt install multiarch-gdb.

To run gdb against a binary being run under QEMU you need two terminals. In one, start the program under test.

QEMU_LD_PREFIX=/usr/aarch64-linux-gnu qemu-aarch64-static -g 1234 ./test-async-sig

The program will start and then wait for gdb to connect on local TCP port 1234.

In the second terminal, run gdb.

gdb-multiarch -q --nh -ex 'set architecture aarch64' -ex 'set sysroot /usr/aarch64-linux-gnu' -ex 'file /home/swebb/projects/libunwind/gh-502/build-linux/tests/test-async-sig' -ex 'target remote localhost:1234' -ex continue -ex bt

In this case, gdb batch mode is being used to connect, continue, and get a stack trace of a crash.