I recently encountered my first need for on-device NDK debugging. While the setup is kind of a pain, its super cool to be able to attach to a remote process over a network socket and have access to the full power of the GNU Debugger. Hopefully this quick guide takes some pain out of the initial setup (root required!).

First, let’s load the ARM64 build of gdbserver on to the phone. This can be found wherever you’ve placed the Android NDK (in my case, the latest version is hanging out in my downloads folder)

➜  ~ export ANDROID_NDK=~/Downloads/android-ndk-r13b/
➜  ~ adb push $ANDROID_NDK/prebuilt/android-arm64/gdbserver/gdbserver /sdcard/gdbserver

Now let’s jump into the phone, gain root access, and have a look at the PATH. For the sake of convenience, we’ll want to move gdbserver to somewhere the system is looking for executables.

➜  ~ adb shell
shell@zeroltetmo:/ $ su root
root@zeroltetmo:/
root@zeroltetmo:/ echo $PATH
/su/bin:/sbin:/vendor/bin:/system/sbin:/system/bin:/su/xbin:/system/xbin

Looks like there are some options for where to put it. /sbin and /system/bin are read-only locations, but /su/bin is writable. Let’s move gdbserver there and make it executable.

root@zeroltetmo:/ cp /sdcard/gdbserver /su/bin
root@zeroltetmo:/ chmod +x /su/bin/gdbserver

With the debug server in place and executable, let’s check that it can execute.

root@zeroltetmo:/ gdbserver --version
GNU gdbserver (GDB) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
gdbserver is free software, covered by the GNU General Public License.
This gdbserver was configured as "aarch64-eabi-linux"

Great! Now for something more cool. Find the Android Talk process and attach the debugger. The ps command lists processes and we’ll use grep to filter. The second column in the result is the PID of the com.google.android.talk process, let’s start the debug server and attach it to that process.

root@zeroltetmo:/ ps | grep talk
u0_a124   10208 3094  1174688 43364 SyS_epoll_ 00f71c2098 S com.google.android.talk
root@zeroltetmo:/ gdbserver :12345 --attach 10208

Now in another terminal from your desktop, we’ll forward the debug ports over the Android USB bridge and try connecting.

➜  ~ adb forward tcp:12345 tcp:12345
➜  ~ $ANDROID_NDK/prebuilt/darwin/bin/gdb
(gdb) target remote :12345

Remote debugging using :12345
Reading /system/bin/app_process32 from remote target...
Reading /system/bin/app_process32 from remote target...
Reading symbols from target:/system/bin/app_process32...(no debugging symbols found)...done.
Reading /system/bin/linker from remote target...
Reading /system/bin/linker from remote target...
Reading symbols from target:/system/bin/linker...(no debugging symbols found)...done.
0xf71c2098 in ?? ()
(gdb)

The system symbols download and we’re presented with an entry point for the debugger. At this point, you can use GDB in exactly the same way as if you were using it locally or from within an IDE. The Quick Reference is your friend, especially if you’ve spent the last few years in Xcode living the lldb high life.