Android (Command Line)

From Crypto++ Wiki
Jump to navigation Jump to search

This page will provide instructions for cross-compiling Crypto++ on the command line for Android. The instructions below use setenv-android.sh and GNUmakefile-cross for cross-compiles. If you want to try an Android.mk and Application.mk, then see the Android.mk wiki article.

There are four steps to building Crypto++ for Android, and the process will create an Android version of cryptest.exe, the dynamic library, and the static library. After building the library, you can push cryptest.exe and the test vectors to the device, and then execute the tests using a remote shell.

While you can use the Crypto++ shared object directly though JNI, you will likely provide an application specific shared object with C-style external interfaces which depends upon the static Crypto++ library. The C-style interface will make your shared object easy to call using JNI; while the Crypto++ static library will allow you to aggressively strip unused code. See Wrapper DLL for details.

setenv-android.sh is a moving target. It is a moving target because Android keeps changing the KDK, the toolchains and the paths to the tools. One version of the script tuned for a particular NDK probably won't work with another NDK version. You can thank the Android team for that.

Three related pages for Android are Android Setup (Command Line), Android.mk and Android Activity. Android Setup (Command Line) discusses how to setup an Android build machine. Android.mk builds the library using Android.mk and Application.mk. Android Activity uses the Crypto++ shared object in an Android activity.

setenv-android.sh

Before we begin, the history of setenv-android.sh needs to be explained. From about 2011 or 2012 until about 2018 the project used setenv-android.sh. It supported Android NDK r6 or so up to about NDK r15. At around NDK r15 the script needed to be rewritten due to changes in the NDK.

In 2018 setenv-android.sh was modified to support the updated NDK. The updated NDK is r15 or r16 and above. A lot of breaking changes occurred in the NDK so the script had a lot of reworking. Thanks to Skycoder for doing the work. Also see PR #546, Apply updated android build rules.

In 2018 Android announced Clang was the new default compiler, and GCC support was going to be dropped. In response our recently updated setenv-android.sh was updated again.

Between the second and third iteration of the script we tried to supply setenv-android-clang.sh and setenv-android-gcc.sh. The GCC and Clang versions of the script were broken experiments. You should not use them.

When you see setenv-android.sh used below in the procedures, please know it is one of the variations listed above.

Set the Environment

Cryptopp-android-100.png

Before you begin you must set the cross-compilation environment. Setting the environment will do two things. First, it will ensure the Android toolchain is on-path. Second, it will ensure some environmental variables are set so the makefile picks up specific device flags. For example, the script will set IS_ANDROID, AOSP_SYSROOT, AOSP_STL_INC and AOSP_STL_LIB.

You must set ANDROID_NDK_ROOT prior to running the script. You should also set ANDROID_SDK_ROOT for that matter, though its not used for cross-compiling. Android recommends setting both ANDROID_NDK_ROOT and ANDROID_SDK_ROOT because the NDK and SDK use the variables internally. For details, see Recommended NDK Directory?.

Perform the following to run the script. The source command is required to make changes to the current shell.

$ source ./setenv-android.sh 
ANDROID_NDK_ROOT is /opt/android-ndk
ANDROID_NDK_ROOT: /opt/android-ndk
AOSP_TOOLCHAIN_PATH: /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64//bin/
AOSP_API: 23
AOSP_RUNTIME: libc++
AOSP_SYSROOT: /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64//sysroot
AOSP_FLAGS: -march=armv7-a -mthumb -mfloat-abi=softfp -funwind-tables -fexceptions -frtti
CPU FEATURES: cpu-features.h and cpu-features.c are present

setenv-android.sh takes two optional arguments. The first optional argument is the architecture. The default is armeabi-v7a. Architectures can be one of:

  • armeabi-v7a
  • arm64-v8a
  • x86
  • x86_64

The second optional argument is the Android API level. The default API is 21. If you don't specify an API then 21 will be used.

# Build for Android 6.0
AOSP_API="android-23" source setenv-android.sh

# Build for Android 6.1
AOSP_API="android-24" source setenv-android.sh

# Build for ARM64 (AARCH64)
$ source setenv-android.sh arm64

# Build for ARM64 (again)
$ source setenv-android.sh armv8a

Note that if more than one library requires the C++ runtime or STL library, then you must use the dynamic version of the library. For example, if you are using Qt and Crypto++, then you must use libc++_shared.so.

Build the Library

Building the library consists of running the following command after setting the environment:

$ make -f GNUmakefile-cross static dynamic
armv7a-linux-androideabi23-clang++ -DNDEBUG -g2 -O3 -fPIC -pipe -Wall -march=arm
v7-a -mthumb -mfloat-abi=softfp -funwind-tables -fexceptions -frtti -stdlib=libc
++ -DANDROID -D__ANDROID_API__=23 --sysroot=/opt/android-ndk/toolchains/llvm/pre
built/linux-x86_64//sysroot -Wa,--noexecstack -c cryptlib.cpp
armv7a-linux-androideabi23-clang++ -DNDEBUG -g2 -O3 -fPIC -pipe -Wall -march=arm
v7-a -mthumb -mfloat-abi=softfp -funwind-tables -fexceptions -frtti -stdlib=libc
++ -DANDROID -D__ANDROID_API__=23 --sysroot=/opt/android-ndk/toolchains/llvm/pre
built/linux-x86_64//sysroot -Wa,--noexecstack -c cpu.cpp
armv7a-linux-androideabi23-clang++ -DNDEBUG -g2 -O3 -fPIC -pipe -Wall -march=arm
v7-a -mthumb -mfloat-abi=softfp -funwind-tables -fexceptions -frtti -stdlib=libc
++ -DANDROID -D__ANDROID_API__=23 --sysroot=/opt/android-ndk/toolchains/llvm/pre
built/linux-x86_64//sysroot -Wa,--noexecstack -c integer.cpp
...

You can verify the library was built for the correct architecture with the following.

$ find . -name cryptest.exe
./cryptest.exe
$ readelf -h ./cryptest.exe | grep -i 'class\|machine'
  Class:                             ELF32
  Machine:                           ARM
$
$ find . -name libcryptopp.so
./libcryptopp.so
$ readelf -h ./libcryptopp.so | grep -i 'class\|machine'
  Class:                             ELF32
  Machine:                           ARM

You can run ndk-depends to verify dependencies:

$ $ANDROID_NDK_ROOT/ndk-depends libcryptopp.so
libcryptopp.so
libstdc++.so
libc++.so
libm.so
libdl.so
libc.so

If you want to use SOLIB versioning, then you must include HAS_SOLIB_VERSION=1 in the make command. For example:

$ make -f GNUmakefile-cross lean HAS_SOLIB_VERSION=1

Strip the Library

To perform dead-code stripping, be sure to link your final executable with -Wl,--gc-sections. If you link your final application against libcryptopp.a then you will see significant gains. If you link your final application against libcryptopp.so then you will experience a small gains.

You can also strip debug symbols from your final executable, but it will make back traces useless. To do so, you can strip by running arm-linux-androideabi-strip with --strip-debug, --strip-unneeded or --strip-all. arm-linux-androideabi-strip is available on path after running setenv-android.sh. See arm-eabi-strip and binary size for details.

$ ls -al cryptest.exe
-rwxr-xr-x  1 jwalton  staff 42860268 Jul 29 00:41 cryptest.exe
$
$ arm-linux-androideabi-strip --strip-debug cryptest.exe
$ ls -al cryptest.exe
-rwxr-xr-x  1 jwalton  staff  6112963 Jul 29 00:41 cryptest.exe
$
$ arm-linux-androideabi-strip --strip-all cryptest.exe
$ ls -al cryptest.exe
-rwxr-xr-x  1 jwalton  staff  2903712 Jul 29 00:41 cryptest.exe
$ ls -al libcryptopp.so 
-rwxr-xr-x  1 jwalton  staff  28668308 Jul 29 00:52 libcryptopp.so
$ 
$ arm-linux-androideabi-strip --strip-debug libcryptopp.so 
$ ls -al libcryptopp.so 
-rwxr-xr-x  1 jwalton  staff  5478765 Jul 29 00:52 libcryptopp.so
$ 
$ arm-linux-androideabi-strip --strip-all libcryptopp.so 
$ ls -al libcryptopp.so 
-rwxr-xr-x  1 jwalton  staff  3808428 Jul 29 00:52 libcryptopp.so

Additionally, there are other tricks for modest gains. See Android NDK: How to Reduce Binaries Size for details.

Push to Device

To test on the device, you will use adb to push cryptest.exe and the test vectors to the device. Once on a device, the program will be executed with the remote shell. To begin, ensure Android's platform-tools are on path (i.e., ANDROID_SDK_ROOT/platform-tools), and your device is recognized:

$ adb devices
List of devices attached 
0380614543dfd297	device
Cryptopp-android-103.png

With a device available, push cryptest.exe, TestData, and TestVectors to /data/local/tmp.

$ adb push cryptest.exe /data/local/tmp
1295 KB/s (43249212 bytes in 32.591s)
$
$ adb push TestData /data/local/tmp/TestData
push: TestData/xtrdh342.dat -> /data/local/tmp/TestData/xtrdh342.dat
push: TestData/xtrdh171.dat -> /data/local/tmp/TestData/xtrdh171.dat
push: TestData/usage.dat -> /data/local/tmp/TestData/usage.dat
...
55 files pushed. 0 files skipped.
471 KB/s (57449 bytes in 0.119s)
$
$ adb push TestVectors /data/local/tmp/TestVectors
push: TestVectors/whrlpool.txt -> /data/local/tmp/TestVectors/whrlpool.txt
push: TestVectors/wake.txt -> /data/local/tmp/TestVectors/wake.txt
push: TestVectors/vmac.txt -> /data/local/tmp/TestVectors/vmac.txt
...
33 files pushed. 0 files skipped.
3643 KB/s (1314149 bytes in 0.352s)

If you linked against libstlport_shared.so or libc++_shared.so, then you will need to push it also:

$ adb push "$AOSP_STL_LIB" /data/local/tmp
3261 KB/s (3547204 bytes in 1.062s)

After pushing, open a remote shell with adb shell and verify all files are present on the device:

$ adb shell
shell@android: $ ls -l /data/local/tmp                                       
drwxrwxr-x shell     shell              2015-12-07 00:37 TestData
drwxrwxr-x shell     shell              2015-12-07 00:38 TestVectors
-rwxrwxrwx shell     shell     26930220 2015-12-07 00:36 cryptest.exe
-rwxrwxrwx shell     shell      5630724 2014-10-17 02:40 libgnustl_shared.so
-rwxrwxrwx shell     shell      3547204 2014-12-10 05:01 libstlport_shared.so

Execute the Program

Cryptopp-android-105.png

With the program and test vectors on the device, execute the test program:

shell@android:/ $ cd /data/local/tmp
shell@android:/data/local/tmp $ ./cryptest.exe v
Using seed: 1374978640

Testing Settings...

passed:  Your machine is little endian.
passed:  sizeof(byte) == 1
passed:  sizeof(word16) == 2
passed:  sizeof(word32) == 4
passed:  sizeof(word64) == 8
passed:  sizeof(hword) == 2, sizeof(word) == 4, sizeof(dword) == 8
...

If you linked against libstlport_shared.so or libc++_shared.so, then you will need to modify LD_LIBRARY_PATH when running cryptest.exe:

shell@android:/ $ cd /data/local/tmp
shell@android:/data/local/tmp $ LD_LIBRARY_PATH=./; ./cryptest.exe v
Using seed: 1374994654

Testing Settings...
...

If you link to both libstlport_shared.so and libcryptopp.so, then you will have to preload libstlport_shared.so:

shell@android:/ $ cd /data/local/tmp
shell@android:/data/local/tmp $ LD_LIBRARY_PATH=./; ./cryptest.exe v
link_image[1936]:  5431 could not load needed library './libcryptopp.so' for './cryptest.exe'
(reloc_library[1285]:  5431 cannot locate '_ZNSt12__node_alloc11_M_allocateERj'...)
CANNOT LINK EXECUTABLE
shell@android:/data/local/tmp $
shell@android:/data/local/tmp $ export LD_PRELOAD=`pwd`/libstlport_shared.so
shell@android:/data/local/tmp $ LD_LIBRARY_PATH=./; ./cryptest.exe v
Using seed: 1375005963

Testing Settings...
...

If all goes well, the tail of cryptest.exe v will display "All tests passed!" as shown below.

Cryptopp-android-106.png

Install the Library

Once the library has been verified on a device, copy the header files (*.h), the dynamic library (libcryptopp.so), and the static library (libcryptopp.a) to a directory for use in other projects. You can run sudo make -f GNUmakefile-cross install PREFIX=/usr/local/cryptopp/android-armeabi-v7a since the libraries are for API 21 and ARMv7 architecture.

Under this scheme, headers will be found at /usr/local/cryptopp/android-armeabi-v7a/include, and the libraries will be found at /usr/local/cryptopp/android-armeabi-v7a/lib. You can use the following with ndk-build. ndk-build will set $(TARGET_ARCH_ABI):

  • CRYPTOPP_INCL := /usr/local/cryptopp/android-$(TARGET_ARCH_ABI)/include
  • CRYPTOPP_LIB  := /usr/local/cryptopp/android-$(TARGET_ARCH_ABI)/lib

All Architectures

If you need to build all the architectures and install them, then the following script will accomplish it for you. Place the script in the root of the Crypto++ folder. Prior to running the script, ensure both ANDROID_NDK_ROOT and ANDROID_SDK_ROOT are set.

$ cat build-all-android.sh 
#!/usr/bin/env bash

for arch in armeabi armeabi-v7a armeabi-v7a-hard arm64-v8a mips mips64 x86 x86_64
do
    source setenv-android.sh $arch
    if [ "$?" -eq "0" ]; then
        make -f GNUmakefile-cross distclean
        make -f GNUmakefile-cross static dynamic
        sudo make -f GNUmakefile-cross install PREFIX=/usr/local/cryptopp/android-$arch
    fi
done

To build arm64-v8a and other new additions, you will need the NDK r10e, and you will need to use the Android 21 API. The script uses them by default.

Loading Shared Objects

Crypto++ or your shared object wrapper can be loaded in a number of different ways on Android. Development environments, like QT, can further complicate matters because of the way it calls dlopen. If you are experiencing an UnsatisfiedLinkError: dlopen failed: library "./obj/local/armeabi-v7a/libcryptopp.so", then please see the following:

Android Activity

Cryptopp-android-prng.png

You can find an example of using prebuilt Crypto++ shared objects on the wiki at Android Activity. Android Activity uses a sample project called Android-PRNG, and it demonstrates two topics. First, it shows you how to integrate a prebuilt Crypto++ library shared object into a Ant-based or NDK-based project. It may work for Android Studio, too. It may even work for Eclipse, depending on how well it imports an Android java project with JNI.

Second, it shows you how to accumulate seed data from sensors that can be used to seed your software based random number generators. A generator should have a fresh seed applied with IncorporateEntropy before each call to GenerateBlock to ensure the fitness of the generator.

If you are using Android Studio, then you may need to add the following. Also see JNI and Gradle in Android Studio on Stack Overflow.

sourceSets.main {
    jni.srcDirs = []
    jniLibs.srcDir 'src/main/libs'
}

Keep in mind you must statically load the libraries in the order that satisfies link dependencies. An example of doing that is:

public class MyClass {
    
    static {
        System.loadLibrary("stlport_shared");
        System.loadLibrary("cryptopp");
        System.loadLibrary("mylib");
    }
    
    ...
}

Android's C++ Library Support now recommends using ReLinker to relieve you of manually loading libraries in the correct order.

NDK Build Flags

The following are taken from the NDK. They show Android's standard build flags.

You can test the current Android flags using the following from Google's JNI sample. It will build the sample Hello-JNI all of the architectures.

android$ git clone https://github.com/googlesamples/android-ndk
android$ cd android-ndk
android$ git checkout android-mk
android$ cd hello-jni

android:hello-jni$ ndk-build APP_PLATFORM=android-20 V=1
...

Options like -DNDEBUG, -Os, -g and -fmessage-length=0 were omitted. Some flags, like -fexception, must be used to ensure exceptions pass through C routines. Other flags must not be used, like -fno-exceptions and -fno-rtti.

NDK r12 flags

The r12 flags are tailored for GCC. Android had not switched to Clang yet.

armeabi

  • -MMD -MP -MF -fpic -ffunction-sections -funwind-tables -fstack-protector -march=armv5te -mtune=xscale -msoft-float -mthumb -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

armeabi-v7a

  • -MMD -MP -MF -fpic -ffunction-sections -funwind-tables -fstack-protector -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp -mthumb -Wl,--fix-cortex-a8 -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

arm64-v8a

  • -MMD -MP -MF -fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -finline-limit=300 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

mips

  • -MMD -MP -MF -fpic -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers -fomit-frame-pointer -funswitch-loops -finline-limit=300 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

mips64

  • -MMD -MP -MF -fpic -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers -fomit-frame-pointer -funswitch-loops -finline-limit=300 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

x86

  • -MMD -MP -MF -ffunction-sections -funwind-tables -fstack-protector -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -finline-limit=300 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

x86_64

  • -MMD -MP -MF -ffunction-sections -funwind-tables -fstack-protector-strong -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -finline-limit=300 -DANDROID -Wa,--noexecstack -Wformat -Werror=format-security
  • Do not use: -fno-exceptions -fno-rtti

NDK r16 flags

The r16 flags are tailored for GCC. Android had not switched to Clang yet.

armeabi-v7a

  • -MMD -MP -MF -fpic -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -fno-integrated-as -g -target armv7-none-linux-androideabi19 -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -DANDROID -D__ANDROID_API__=19 -Wa,--noexecstack -Wformat -Werror=format-security --sysroot /opt/android-ndk-r16b/sysroot -isystem /opt/android-ndk-r16b/sysroot/usr/include/arm-linux-androideabi

arm64-v8a

  • -MMD -MP -MF ./obj/local/arm64-v8a/objs-debug/hello-jni/hello-jni.o.d -gcc-toolchain /opt/android-ndk-r16b/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64 -target aarch64-none-linux-android -ffunction-sections -funwind-tables -fstack-protector-strong -fpic -no-canonical-prefixes -DANDROID -D__ANDROID_API__=21 -Wa,--noexecstack -Wformat -Werror=format-security --sysroot /opt/android-ndk-r16b/sysroot -isystem /opt/android-ndk-r16b/sysroot/usr/include/aarch64-linux-android

x86

  • -MMD -MP -MF -target i686-none-linux-android -ffunction-sections -funwind-tables -fstack-protector-strong -fPIC -no-canonical-prefixes -DANDROID -D__ANDROID_API__=19 -Wa,--noexecstack -Wformat -Werror=format-security -mstackrealign --sysroot /opt/android-ndk-r16b/sysroot -isystem /opt/android-ndk-r16b/sysroot/usr/include/i686-linux-android

x86_64

  • -MMD -MP -MF -target x86_64-none-linux-android -ffunction-sections -funwind-tables -fstack-protector-strong -fPIC -no-canonical-prefixes -DANDROID -D__ANDROID_API__=21 -Wa,--noexecstack -Wformat -Werror=format-security --sysroot /opt/android-ndk-r16b/sysroot -isystem /opt/android-ndk-r16b/sysroot/usr/include/x86_64-linux-android

NDK r19 flags

The r19 flags are tailored for Clang. Android dropped support for GCC.

TODO...

Downloads

None. The files are present in GitHub.