What is a Cross-Compiler?
How is that connected with an Android?
In order to create a native C/C++ binary for an Android, you must firstly compile the source code. Usually you can't do so on an Android itself due to lack of proper tools and environment, or hardware barriers, especially amount of RAM. This is why you should learn how to cross-compile, to create a binary on your PC, that your ARM-based Android will understand.
Why do I need it?
You need to learn cross-compiling technique if you want to run native C/C++ programs on an Android. Actually, if you've already built your own custom ROM from AOSP sources (i.e. CyanogenMod), then you used cross-compiling tools and methods even without noticing :).
Building an AOSP ROM is fairly easy, there's one command like brunch, which does the job. However, what if you want to compile a custom, not natively included binary? This is the purpose of this tutorial.
What I will learn from this guide?
Step 1 - The Beginning
You should start from installing any Linux-based OS, I highly suggest trying a Debian-based distro (such as Ubuntu), or even Debian itself, as this tutorial is based on it :).
In general, I highly suggest to compile an AOSP ROM (such as CyanogenMod) for your device firstly. This will help you to get familiar with cross-compiling on Android. I also suggest to compile one or two programs from source for your Linux, but if you're brave enough to learn cross-compiling without doing any of these, you can skip those suggestions :).
Step 2 - Setting up
Firstly you should make sure that you have all required compile tools already.
This should do the trick and install all required components.
I suggest creating a new folder and navigating to it, just to avoid a mess, but you can organize everything as you wish.
Start from downloading NDK from here.
Now you should make a standalone toolchain, navigate to root of your ndk (this is important) and then build your toolchain:
You should edit bolded variables to your preferences. Toolchain is the version of GCC you want to use, 4.8 is currently the newest one, in the future it may be 4.9 and so on. Platform is a target API for your programs, this is important only for android-specific commands, such as logging to logcat. When compiling a native Linux program, this won't matter (but it's a good idea to set it properly, just in case). Install dir specifies destination of your toolchain, make sure that it's other than ndk (as you can see I have ndk in /root/ndk and toolchain in /root/ndkTC).
Now you need to download my exclusive cc.sh script from here and make it executable.
This script is a very handy tool written by me to make your life easier while cross-compiling. Before running it make sure to edit "BASIC" options, especially NDK paths. Apart from that it's a good idea to take a look at DEVICEFLAGS and setting them properly for your device, or clearing for generic build. You don't need to touch other ones unless you want/need them.
Just for a reference, I'll include currently editable options:
As you can notice, my magic script already contains bunch of optimizations, especially device-based optimizations, which are the most important. Now it's the time to run our script, but in current shell and not a new one.
Command "source cc.sh" executes cc.sh and "shares" the environment, which means that any exports will be exported to our current shell, and this is what we want. It acts the same as AOSP's ". build/envsetup.sh", so you can also use . instead of source.
As you can see above, my script should let you know if it properly set everything, especially if $CC points to our ndkTC. It also set a generic "$CCC" and "$CCXX" commands, which are optimized versions of standard $CC. $CC points to our cross-compiler, $CCC points to our cross-compiler and also includes our optimization flags.
Step 3 - Cross-Compiling
Now we'll compile our first program for Android!
Create a new file hello.c, and put inside:
Now you compile and strip it:
Remember that $CCC and $SSTRIP command will only work if you source'd cc.sh script explained above. $CCC command compiles source code to a binary with already optimized flags (device flags, optimization level, optimization flags, linker flags), while $SSTRIP command strips "bloat" from output binary, such as comments and notices. The purpose is to make a binary smaller and faster.
You can check if your binary has been compiled properly through readelf command.
As you can see, I've compiled a binary optimized for ARM v7, with THUMB-2 instructions and NEON support. How nice! Is it because of device-specific flags? Let's check what happens if we use $CC instead of $CCC:
As you can see, if you do not specify flags, you'll lose major portion of optimizations. Of course binary will work properly, hence it has been cross-compiled for ARM, but we can always make it smaller and faster! :)
Step 4 - Testing
A favourite part of everything, tests! :)
In above example I pushed my binary straight to /system/bin directory, which is in the Android's PATH. If you don't have rooted device that's not a problem, you can use /data/local directory or /storage/sdcard0. You can also upload your binary anywhere you want and download it as any other file, then run from /storage/sdcard0/Download, this way doesn't require even working ADB :). Just don't forget about setting proper permissions afterwards!
Now let's try to run it! :)
If your binary is not in the PATH, you should write full path to your binary of course. As I pushed my binary to /system/bin, I don't need to do so.
If everything finished successfully and you got your very first Hello World response as above, congratulations. You've just compiled and ran your first native C/C++ program on Android device.
What to do next?
In theory, you can now compile anything you want. Here are some apps that I'm using in my ArchiDroid ROM:
These are only a few examples. You can compile anything you want, or even write your own native applications. Good luck!
Quote:
A cross compiler is a compiler capable of creating executable code for a platform other than the one on which the compiler is running. Cross compiler tools are used to generate executables for embedded system or multiple platforms. It is used to compile for a platform upon which it is not feasible to do the compiling, like microcontrollers that don't support an operating system. From wikipedia |
How is that connected with an Android?
In order to create a native C/C++ binary for an Android, you must firstly compile the source code. Usually you can't do so on an Android itself due to lack of proper tools and environment, or hardware barriers, especially amount of RAM. This is why you should learn how to cross-compile, to create a binary on your PC, that your ARM-based Android will understand.
Why do I need it?
You need to learn cross-compiling technique if you want to run native C/C++ programs on an Android. Actually, if you've already built your own custom ROM from AOSP sources (i.e. CyanogenMod), then you used cross-compiling tools and methods even without noticing :).
Building an AOSP ROM is fairly easy, there's one command like brunch, which does the job. However, what if you want to compile a custom, not natively included binary? This is the purpose of this tutorial.
What I will learn from this guide?
- How to properly set C/C++ building environment
- How to build a native C/C++ application for Android device
- How to optimize native binaries for my device
Step 1 - The Beginning
You should start from installing any Linux-based OS, I highly suggest trying a Debian-based distro (such as Ubuntu), or even Debian itself, as this tutorial is based on it :).
In general, I highly suggest to compile an AOSP ROM (such as CyanogenMod) for your device firstly. This will help you to get familiar with cross-compiling on Android. I also suggest to compile one or two programs from source for your Linux, but if you're brave enough to learn cross-compiling without doing any of these, you can skip those suggestions :).
Step 2 - Setting up
Firstly you should make sure that you have all required compile tools already.
Quote:
root@ArchiDroid:~# apt-get update && apt-get install checkinstall |
I suggest creating a new folder and navigating to it, just to avoid a mess, but you can organize everything as you wish.
Start from downloading NDK from here.
Quote:
The NDK is a toolset that allows you to implement parts of your app using native-code languages such as C and C++. |
Quote:
root@ArchiDroid:~# wget http://dl.google.com/android/ndk/and...x86_64.tar.bz2 root@ArchiDroid:~# tar xvf android-ndk-r9d-linux-x86_64.tar.bz2 root@ArchiDroid:~# mv android-ndk-r9d ndk |
Quote:
root@ArchiDroid:~# cd ndk/ root@ArchiDroid:~/ndk# build/tools/make-standalone-toolchain.sh --toolchain=arm-linux-androideabi-4.8 --platform=android-18 --install-dir=/root/ndkTC Copying prebuilt binaries... Copying sysroot headers and libraries... Copying libstdc++ headers and libraries... Copying files to: /root/ndkTC Cleaning up... Done. |
Now you need to download my exclusive cc.sh script from here and make it executable.
Quote:
root@ArchiDroid:~# wget https://dl.dropboxusercontent.com/u/...79/Files/cc.sh root@ArchiDroid:~# chmod 755 cc.sh |
Just for a reference, I'll include currently editable options:
As you can notice, my magic script already contains bunch of optimizations, especially device-based optimizations, which are the most important. Now it's the time to run our script, but in current shell and not a new one.
Quote:
root@ArchiDroid:~# source cc.sh Done setting your environment CFLAGS: -O2 -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp -s -flto=8 -ffunction-sections -fdata-sections -fvisibility=hidden -funswitch-loops -frename-registers -frerun-cse-after-loop -fomit-frame-pointer -fgcse-after-reload -fgcse-sm -fgcse-las -fweb -ftracer -fstrict-aliasing -DNDEBUG -D__ANDROID__ LDFLAGS: -Wl,-O1 -Wl,--as-needed -Wl,--relax -Wl,--sort-common -Wl,--gc-sections CC points to arm-linux-androideabi-gcc and this points to /root/ndkTC/bin/arm-linux-androideabi-gcc Use "$CC" command for calling gcc and "$CCC" command for calling our optimized CC Use "$CXX" command for calling g++ and "$CCXX" for calling our optimized CXX Use "$STRIP" command for calling strip and "$SSTRIP" command for calling our optimized STRIP Example: "$CCC myprogram.c -o mybinary && $SSTRIP mybinary " When using makefiles with configure options, always use "./configure $CONFIGANDROID" instead of using "./configure" itself Please notice that makefiles may, or may not, borrow our CFLAGS and LFLAGS, so I suggest to double-check them and eventually append them to makefile itself Pro tip: Makefiles with configure options always borrow CC, CFLAGS and LDFLAGS, so if you're using ./configure, probably you don't need to do anything else |
As you can see above, my script should let you know if it properly set everything, especially if $CC points to our ndkTC. It also set a generic "$CCC" and "$CCXX" commands, which are optimized versions of standard $CC. $CC points to our cross-compiler, $CCC points to our cross-compiler and also includes our optimization flags.
Quote:
root@ArchiDroid:~# echo $CC arm-linux-androideabi-gcc root@ArchiDroid:~# echo $CCC arm-linux-androideabi-gcc -O2 -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp -s -flto=8 -ffunction-sections -fdata-sections -fvisibility=hidden -funswitch-loops -frename-registers -frerun-cse-after-loop -fomit-frame-pointer -fgcse-after-reload -fgcse-sm -fgcse-las -fweb -ftracer -fstrict-aliasing -DNDEBUG -D__ANDROID__ -Wl,-O1 -Wl,--as-needed -Wl,--relax -Wl,--sort-common -Wl,--gc-sections |
Step 3 - Cross-Compiling
Now we'll compile our first program for Android!
Create a new file hello.c, and put inside:
Code:
#include <stdio.h>
int main (void)
{
puts ("Hello World!");
return 0;
}
Quote:
root@ArchiDroid:~# $CCC hello.c -o hello && $SSTRIP hello |
You can check if your binary has been compiled properly through readelf command.
Quote:
root@ArchiDroid:~# readelf -A hello Attribute Section: aeabi File Attributes Tag_CPU_name: "ARM v7" Tag_CPU_arch: v7 Tag_CPU_arch_profile: Application Tag_ARM_ISA_use: Yes Tag_THUMB_ISA_use: Thumb-2 Tag_FP_arch: VFPv3 Tag_Advanced_SIMD_arch: NEONv1 Tag_ABI_PCS_wchar_t: 4 Tag_ABI_FP_denormal: Needed Tag_ABI_FP_exceptions: Needed Tag_ABI_FP_number_model: IEEE 754 Tag_ABI_align_needed: 8-byte Tag_ABI_enum_size: int Tag_ABI_HardFP_use: SP and DP Tag_ABI_optimization_goals: Aggressive Speed Tag_CPU_unaligned_access: v6 Tag_DIV_use: Not allowed |
Quote:
root@ArchiDroid:~# readelf -A hello2 Attribute Section: aeabi File Attributes Tag_CPU_name: "5TE" Tag_CPU_arch: v5TE Tag_ARM_ISA_use: Yes Tag_THUMB_ISA_use: Thumb-1 Tag_FP_arch: VFPv2 Tag_ABI_PCS_wchar_t: 4 Tag_ABI_FP_denormal: Needed Tag_ABI_FP_exceptions: Needed Tag_ABI_FP_number_model: IEEE 754 Tag_ABI_align_needed: 8-byte Tag_ABI_enum_size: int Tag_ABI_optimization_goals: Aggressive Speed Tag_DIV_use: Not allowed |
Step 4 - Testing
A favourite part of everything, tests! :)
Quote:
root@ADB:~/shared# adb shell root@m0:/ # sysrw root@m0:/ # exit root@ADB:~/shared# adb push hello /system/bin/hello 95 KB/s (5124 bytes in 0.052s) root@ADB:~/shared# adb shell root@m0:/ # chmod 755 /system/bin/hello root@m0:/ # chown root:system /system/bin/hello root@m0:/ # exit |
Now let's try to run it! :)
If your binary is not in the PATH, you should write full path to your binary of course. As I pushed my binary to /system/bin, I don't need to do so.
If everything finished successfully and you got your very first Hello World response as above, congratulations. You've just compiled and ran your first native C/C++ program on Android device.
What to do next?
In theory, you can now compile anything you want. Here are some apps that I'm using in my ArchiDroid ROM:
These are only a few examples. You can compile anything you want, or even write your own native applications. Good luck!