In this tutorial, compilation and remote debugging of a simple C program on a rooted Android with ARM 64-bit architecture is described. The computer's hostname is ArchLinux and the smartphone Asus Zenfone is running on Android 5.0.2:
Install a cross-compiler toolchain
Install Android NDK (Native Development Kit):
1 |
[johndoe@ArchLinux]% yaourt -S android-ndk |
Install ARM 64-bit toolchain by giving the full path (/opt/arm64cross):
1 2 3 4 5 6 7 8 |
[johndoe@ArchLinux]% /opt/android-ndk/build/tools/make-standalone-toolchain.sh --arch=arm64 --platform=android-21 --install-dir=/opt/arm64cross --force HOST_OS=linux HOST_EXE= HOST_ARCH=x86_64 HOST_TAG=linux-x86_64 HOST_NUM_CPUS=4 BUILD_NUM_CPUS=8 Toolchain installed to /opt/arm64cross. |
Add folder with binaries to the $PATH environment variable:
1 |
[johndoe@ArchLinux]% export PATH=$PATH:/opt/arm64cross/bin |
Check the compiler:
1 2 3 4 5 6 7 8 |
[johndoe@ArchLinux]% aarch64-linux-android-gcc -v Using built-in specs. COLLECT_GCC=aarch64-linux-android-gcc COLLECT_LTO_WRAPPER=/opt/arm64cross/bin/../libexec/gcc/aarch64-linux-android/4.9.x/lto-wrapper Target: aarch64-linux-android Configured with: /usr/local/google/buildbot/src/android/gcc/toolchain/build/../gcc/gcc-4.9/configure --prefix=/tmp/8ee0b8157b3409c4b84fff35696d6c90 --target=aarch64-linux-android --host=x86_64-linux-gnu --build=x86_64-linux-gnu --with-gnu-as --with-gnu-ld --enable-languages=c,c++ --with-gmp=/buildbot/tmp/build/toolchain/temp-install --with-mpfr=/buildbot/tmp/build/toolchain/temp-install --with-mpc=/buildbot/tmp/build/toolchain/temp-install --with-cloog=/buildbot/tmp/build/toolchain/temp-install --with-isl=/buildbot/tmp/build/toolchain/temp-install --with-ppl=/buildbot/tmp/build/toolchain/temp-install --disable-ppl-version-check --disable-cloog-version-check --disable-isl-version-check --enable-cloog-backend=isl --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --disable-libssp --enable-threads --disable-nls --disable-libmudflap --disable-libgomp --disable-libstdc__-v3 --disable-sjlj-exceptions --disable-shared --disable-tls --disable-libitm --enable-bionic-libs --enable-libatomic-ifuncs=no --enable-initfini-array --disable-nls --prefix=/tmp/8ee0b8157b3409c4b84fff35696d6c90 --with-sysroot=/tmp/8ee0b8157b3409c4b84fff35696d6c90/sysroot --with-binutils-version=2.25 --with-mpfr-version=3.1.1 --with-mpc-version=1.0.1 --with-gmp-version=5.0.5 --with-gcc-version=4.9 --with-gdb-version=none --with-gxx-include-dir=/tmp/8ee0b8157b3409c4b84fff35696d6c90/include/c++/4.9.x --with-bugurl=http://source.android.com/source/report-bugs.html --enable-languages=c,c++ --disable-bootstrap --enable-plugins --enable-libgomp --enable-gnu-indirect-function --disable-libsanitizer --enable-gold --enable-ld=default --enable-threads --enable-eh-frame-hdr-for-static --enable-fix-cortex-a53-835769 --enable-graphite=yes --with-isl-version=0.11.1 --with-cloog-version=0.18.0 --program-transform-name='s&^&aarch64-linux-android-&' --enable-gold Thread model: posix gcc version 4.9.x 20150123 (prerelease) (GCC) |
Compile a simple program
The "Hello, World!" program:
1 2 3 4 5 6 7 |
#include <stdio.h> int main() { printf("Hello World!\n"); } |
Makefile for helloworld.c program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
CC = aarch64-linux-android24-clang CFLAGS = -c -v -fPIE -g LINK = aarch64-linux-android24-clang LFLAGS = -v -fPIE -g -pie SOURCES = helloworld.c OBJECTS = $(SOURCES:.c=.o) DEPS = $(SOURCES:.c=.d) EXECUTABLE = $(SOURCES:.c=) $(EXECUTABLE): $(OBJECTS) $(LINK) $^ -o $@ $(LFLAGS) $(OBJECTS): %.o: %.c $(CC) $(CFLAGS) -MMD -MF $(patsubst %.o,%.d,$@) -o $@ $< clean: rm $(OBJECTS) $(EXECUTABLE) $(DEPS) |
Put them into one folder and compile by running GNU make:
1 |
[johndoe@ArchLinux]% make |
Examine the generated executable:
1 2 |
file helloworld helloworld: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, with debug_info, not stripped |
The generated file is actually a shared object because it was compiled using "-fPIE" flag and linked with "-pie" option to support position-independent code. This is a requirement of Android OS. Executables compiled without these options will not work! Otherwise you will get an error:
1 2 |
user@ASUS_Z00L_63:/data/local/tmp $ /data/local/tmp/helloworld "/data/local/tmp/helloworld": error: Android 5.0 and later only support position-independent executables (-fPIE). |
Here is an excerpt from gcc's manual page:
1 2 3 |
-fPIE These options are similar to -fpic and -fPIC, but the generated position-independent code can be only linked into executables. Usually these options are used to compile code that will be linked using the -pie GCC option. |
Note that "-mandroid" and "-mbionic" flags were not used here, and as it turns out, the generated executable works fine and is linked to a necessary "libc" correctly. Option "-g" will make the compiler embed debugging information into the executable.
Set up an SSH server on Android
On the Android device, install SimpleSSHD app from F-Droid or Google Play. Open the app and start the SSH server. By default it listens to port 2222. Now there is an authorization problem. It is solved by copying your public key to file "authorized_keys" in SimpleSSHD app's folder:
1 |
[johndoe@ArchLinux]% adb push ~/.ssh/id_rsa.pub /data/local/tmp |
Now open ADB shell:
1 |
[johndoe@ArchLinux]% adb shell |
Switch user to root and copy the public key:
1 2 3 4 |
shell@ASUS_Z00L_63:/ $ su root@ASUS_Z00L_63:/ # mv /data/local/tmp/id_rsa.pub /data/data/org.galexander.sshd/files/authorized_keys root@ASUS_Z00L_63:/ # ls -la /data/data/org.galexander.sshd/files/authorized_keys -rw------- u0_a65 u0_a65 745 2020-11-06 18:07 authorized_keys |
You may need to change the ownership and mode of "authorized_keys" file:
1 2 |
root@ASUS_Z00L_63:/ # chown u0_a65:u0_a65 /data/data/org.galexander.sshd/files/authorized_keys root@ASUS_Z00L_63:/ # chmod 600 /data/data/org.galexander.sshd/files/authorized_keys |
Remote debugging using gdbserver
Restart the SimpleSSHD server and copy the "helloworld" program as well as gdbserver to the Asus smartphone with IP address 192.168.0.13:
1 2 |
[johndoe@ArchLinux]% scp -P 2222 helloworld shell@192.168.0.13:/data/local/tmp [johndoe@ArchLinux]% scp -P 2222 /opt/android-ndk/prebuilt/android-arm64/gdbserver/gdbserver shell@192.168.0.13:/data/local/tmp |
Login using SSH client from the PC (ArchLinux) and start gdbserver running "helloworld" executable on Android:
1 2 3 |
[johndoe@ArchLinux]% ssh -p 2222 shell@192.168.0.13 user@ASUS_Z00L_63:/data/local/tmp $ cd /data/local/tmp user@ASUS_Z00L_63:/data/local/tmp $ ./gdbserver --no-startup-with-shell :2345 helloworld |
Now start the gdb client on ArchLinux:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[johndoe@ArchLinux]% /opt/android-ndk/prebuilt/linux-x86_64/bin/gdb GNU gdb (GDB) 8.3 Copyright (C) 2019 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". (gdb) |
Connect to the remote gdbserver and set a breakpoint at the "main" function of the "helloworld" program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(gdb) target remote 192.168.0.13:2345 Remote debugging using 192.168.0.13:2345 Reading /data/local/tmp/helloworld from remote target... warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead. Reading /data/local/tmp/helloworld from remote target... Reading symbols from target:/data/local/tmp/helloworld... Reading /system/bin/linker64 from remote target... Reading /system/bin/linker64 from remote target... Reading symbols from target:/system/bin/linker64... (No debugging symbols found in target:/system/bin/linker64) 0x0000007fb7fdf648 in __dl__start () from target:/system/bin/linker64 (gdb) break main Breakpoint 1 at 0x5581069670: file helloworld.c, line 5. (gdb) |
Continue running and examining the code:
1 2 3 4 5 6 7 8 9 10 |
(gdb) continue Continuing. Breakpoint 1, main () at helloworld.c:5 5 printf("Hello World!\n"); (gdb) list 1 #include <stdio.h> 2 3 int main() 4 { 5 printf("Hello World!\n"); 6 7 } (gdb) |
Disassemble the code and examine memory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
(gdb) disass Dump of assembler code for function main: 0x0000005581069668 <+0>: stp x29, x30, [sp, #-16]! 0x000000558106966c <+4>: mov x29, sp => 0x0000005581069670 <+8>: adrp x0, 0x5581069000 0x0000005581069674 <+12>: add x0, x0, #0x688 0x0000005581069678 <+16>: bl 0x55810695c0 <puts@plt> 0x000000558106967c <+20>: ldp x29, x30, [sp], #16 0x0000005581069680 <+24>: ret End of assembler dump. (gdb) x /s 0x5581069688 0x5581069688: "Hello World!" (gdb) continue Continuing. [Inferior 1 (process 29699) exited with code 012] (gdb) |
The gdbserver process in Android (SSH client window) should exit and print "Hello World!" message:
1 2 3 4 5 6 7 8 9 10 |
user@ASUS_Z00L_63:/data/local/tmp $ ./gdbserver --no-startup-with-shell :2345 helloworld gdbserver: Unable to determine the number of hardware watchpoints available. gdbserver: Unable to determine the number of hardware breakpoints available. Process /data/local/tmp/helloworld created; pid = 29699 Listening on port 2345 Remote debugging from host 192.168.0.133, port 46512 Hello World! Child exited with status 10 user@ASUS_Z00L_63:/data/local/tmp $ |
The screencast (text is copy-pastable) is shown below:
There is no need to use SSHD server, everything can be done through adb by establishing port forwarding to localhost:
1 2 3 4 |
[johndoe@ArchLinux]% adb forward tcp:2345 tcp:2345 [johndoe@ArchLinux]% /opt/android-ndk/prebuilt/linux-x86_64/bin/gdb (gdb) target remote localhost:2345 ... |