Cross-compiling for Android

July 2019

I recently ran into the problem that I could not set hardware breakpoints or watchpoints in gdb, when running on a Pixel 3 device. Long story short, the version of gdb was too old and I needed to compile a newer version by myself. Along the way I made some notes on cross-compiling code for Android, which am sharing in this post. The aim is to use the llvm toolchain provided with the Android NDK.

autotools

A good starting point for mixing the NDK with autotools is [this page on developer.android.com][other-build-system]. A good addition to that article is the Cross-compilation using Clang page which describes the concept of target triples, as well as the lesser known compiler flags. To top it off this StackOverflow page explains the difference between the build, host and target triples as seen in autotools. These three articles provide a good understanding on how to mix llvm and autotools when cross-compiling.

The main idea is to export a set of environment variables which are caught by autoconf (part of autotools). In a nutshell it looks like this

export NDK=[path to the android ndk]
export HOST_TAG=linux-x86_64
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAG
export AR=$TOOLCHAIN/bin/aarch64-linux-android-ar
export AS=$TOOLCHAIN/bin/aarch64-linux-android-as
export CC=$TOOLCHAIN/bin/aarch64-linux-android29-clang
export CXX=$TOOLCHAIN/bin/aarch64-linux-android29-clang++
export LD=$TOOLCHAIN/bin/aarch64-linux-android-ld
export RANLIB=$TOOLCHAIN/bin/aarch64-linux-android-ranlib
export STRIP=$TOOLCHAIN/bin/aarch64-linux-android-strip

gdb

In the case of gdb, the binaries provided by the Android NDK are compiled using a build-gdb.sh script. From that script we can borrow the actual flags provided to ./configure. What worked for me was

./configure \
--with-build-sysroot=$NDK/toolchains/llvm/prebuilt/$HOST_TAG/sysroot \
--disable-shared \
--enable-static \
--build=x86_64 \
--host=aarch64-linux-android \
--enable-targets=aarch64-linux-android \
--disable-werror \
--disable-nls \
--disable-docs \
--without-mpc \
--without-mpfr \
--without-gmp \
--without-cloog \
--without-isl \
--without-sim \
--enable-gdbserver=yes

strace

Another tool good to have is strace, preferably built as a static binary which can simply be pushed on a system and used directly. Building a static binary requires the extra CFLAGS environment variable. The arguments to ./configure are

export CFLAGS='-static'
./configure \
--prefix=$(pwd)/install \
--build=x86_64 \
--host=aarch64-linux-android

libyaml & libcyaml

As a generic example for compiling and linking libraries where one is a dependency of the other, a good example are libyaml and libcyaml. Note though that libcyaml does not use autotools, but does follow the standard names for environment variables used to modify the calls to the compiler and the linker.

Building libyaml is straightforward and can be done exactly the same as strace. In order to build libcyaml, additional environment variables must be exported to locate the header and shared object files.

export CFLAGS=-I[path to libyaml install folder]/include
export LDFLAGS=-L[path to libyaml install folder]/lib

cmake

Next to autotools, another popular build system is cmake, which is supported as well on developer.android.com. The ndk itself provides a toolchain file with path sdk/ndk-bundle/build/cmake/android.toolchain.cmake and on android.googlesource.com. Note though that the toolchain file provided by the sdk is different than the one provided by the cmake project itself.

Integration of the toolchain file into an existing CMakeLists.txt file requires setup of several variables. What worked for me is something like

set(ANDROID_SDK_PATH [path to the android sdk])
set(ANDROID_ABI arm64-v8a)
set(ANDROID_NATIVE_API_LEVEL 28)
set(ANDROID_NDK ${ANDROID_SDK_PATH}/ndk-bundle)
set(ANDROID_PLATFORM android-28)
set(ANDROID_STL c++_shared)
set(ANDROID_TOOLCHAIN clang)
set(CMAKE_TOOLCHAIN_FILE ${ANDROID_SDK_PATH}/ndk-bundle/build/cmake/android.toolchain.cmake)

Table of contents