How-to debug native code with Android

This is a step by step guide for executing and debugging native code on Android with Eclipse.

1. Prerequisites

The SDK version used for this guide is Froyo with the NDK r4b ( crystax release ).
Also Eclipse CDT plugin it’s very useful for our purposes, so install it.
Last plugin to install it’s the Sequoyah plugin for Eclipse.

2. Project setup

At this point let’s create a new Android project, name it “Example” and use the “com.darkwavegames.com” package name, add also an Activity name of your choice.
Select Android 2.2 as base SDK version and complete the project wizard.

Now you need to add the native support for the newly created project. Just right click on the project root element in the package explorer and select “add native support”.

In the next dialog write the path of your NDK folder and give also name for your library.
After this operation a new folder “jni” will be created with a .cpp file, header filer and an Android makefile, Android.mk, which can be edited to modify all the includes, linker and compiler options. In the Android.mk file you also need to specify all the source file you want to use within the LOCAL_SRC_FILE directive.

3. Debug

In order to enable debug of native code in Android you have to face different problems, based also on the device and the firmware version, and if the native code is multi thread or single thread.
First of all you need to mofify the AndroidManifest.xml file adding the attribute “debuggable” to true ( remember also to enable the “Debug USB” option under the Application device menu ).
At this point you can debug all the java code within your eclipse debugger, for for the native C debug you need more steps.

3.1 Rooting

In case one of the below prerequisites are not available you need to root the device. For most of the device you just need to download from the market the application z4root.

3.2 PackageManagerService

The first thing needed in order to let the gdb-debug script works is the presence of the “com.android.server.PackageManagerServices” service on the device. This serve generates, for each installed application, a new entry inside the “/data/system/packages.list” file.
This file is used by the the “run-as” command to know the process privileges and its folder content.
If on your device this service is unavailable you need to create one.

( Just skip this step if you have the PackageManagerService on your device )
The file format is very simple. every line has 4 columns: package name, user id, debug flasg (“0″ or “1”), applicatio folder.
You can do this: from Eclipse install the application just using the “Run as” command ( or via shell writing: adb install package.apk ). Now in a new terminal window write:

1
host:~ $ adb shell ps | grep com.darkwavegames.esempio

First output entry will be the user associated with that process ( let’s say app_42 ). In order to obtain the user_id simply write:

1
host:~ $ adb shell su app_42

Just copy the output ( which is the user_id, for instance 10042 ) and exit from shell ( using “exit” command).
Now we have to get the app folder. Unfortunately the /data/data folder is protected, so you need to log into the device and obtain root access.

1
2
3
host:~ $ adb shell su
# cd /data/data
# ls -l -h

Look for the folder  associated to our user ( in our example is: com.darkwavegames.esempio ).
Now we have all the informations for creating an entry for the packages.list:

1
2
3
host:~ $ adb shell su
# cd /data/system
# echo com.darkwavegames.esempio 10042 1 /data/data/com.darkwavegames.esempio > packages.list

Now the run-as command will find all the required informations.
Note
that in case you uninstall and reinstall the application you need to to repeat this steps because the system could have assigned a new user_id to that application.
Now you should be able to run the ndk-gdb script ( executing it inside the project folder ) and set breakpoints on the native code.

3.3 NDK-GDB

At this point starting ndk-gdb from the project folder should be enough. If you’ve verified all the above prerequisites and your code is simgle thread this should be enough.
Instead, if your native code is multi thread, the gdbserver which comes with the r4b can’t debug your code ( probably it has been compiled without the multi thread support ).

In this case you need to obtain from the android repository git://android.git.kernel.org/platform/prebuilt.git the “prebuilt” folder of the SDK. Now that you have download it into your $PREBUILT folder you need to download the gdbserver with multi thread support from this link.
Copy the gdbserver just downoaded into folder $ANDROID NDK PATH/build/prebuilt/$HOST ARCH/arm-eabi-4.4.0/bin where $HOST_ARCH is your developement architecture ( darwin-x86 if you’re with mac osx ).
Copy $PREBUILT/$HOST ARCH/toolchain/arm-eabi-4.4.0/bin/arm-eabi-gdb into $ANDROID NDK PATH/build/prebuilt/$HOST ARCH/arm-eabi-4.4.0/bin and use it as debugger.
We’re close to the goal!

Now modify the “install-binary.mk” script inside $ANDROID_NDK_PATH and change the line

1
$(call cmd-strip, $(PRIVATE_DST))

into

1
2
3
ifneq ($(APP_OPTIM),debug)
$(call cmd-strip, $(PRIVATE_DST))
endif

In this way, if in out Android.mk file there’s a directive APP_OPTIM with the value “debug”, the symbol strip will not be executed.

Now in order to have the debug running we need to add a breakpoint in the Java code just before the System.loadLibrary() method and start a new debug session from Eclipse.
As the code reach the breakpoint, open the terminal, enter the project folder and write these commands:

1
2
3
4
5
host:~ $ adb shell ps | grep com.darkwavegames.esempio app_42 4512 2377 648 332 c0318ffc afd0eafc S com.d...
host:~ $ adb shell
$ su
# cd /data/data/com.darkwavegames.esempio/lib
# ./gdbserver :5039 --attach 4512

Without close terminal, open a new termina window and execute:

1
2
3
4
5
6
7
8
host:~ $ cd workspace/esempio
host:esempio $ adb forward tcp:5039 tcp:5039
host:esempio $ adb pull /system/bin/app_process \
obj/local/armeabi/app_process
host:esempio $ adb pull /system/lib/libc.so \
obj/local/armeabi/libc.so
host:esempio $ export NDK_BIN=$ANDROID_NDK_ROOT/build/prebuilt
host:esempio $ $NDK_BIN/darwin-x86/arm-eabi-4.4.0/bin/arm-eabi-gdb \ -x obj/local/armeabi/gdb.setup -e obj/local/armeabi/app_process

gdb.setup is generated by the ndk-gdb, but is created into the libs/armeabi folder. It’s important to copy it into the “obj” folder and add the line “target remote :5039″.
Now from the gdb prompt it’s possible to add breakpoints and execute all the debug instructions such as step into, over, continue…

All these steps are included in the “remotegdb.sh” file, so you don’t need to write all these commands every time! Just execute this script in the project folder ( the same folder where the AndroidManifest.xml is located ).

Lead Android developer at Aviary and New Yorker since 2011. Mixed marketing and artistic background with more than 10 years of experience with Flash actionscript and python before switching to android development.
  • Pingback: Tweets that mention How-to debug native code with Android -- Topsy.com

  • http://www.facebook.com/sudhaker.dr Dhondi Sudhaker

    Hi I am looking out for something like this to debug the native code.

    but in step 2 after I add the native support for the android project, my project will not compile…
    in between it pops up the “IncrediBuild installer” and waits until the installer is quit then again continues and shows the same installed the second time as well then after i cancel the installer it will show an error saying compilation failed.

    My question is “Are you using any feature of IncrediBuild from xoreax?”

    Below is the log when i compile the example created.

    **** Build of configuration Default for project NativeExample ****

    bash D:android-ndk-r4-crystaxndk-build V=1
    cygwin warning:
    MS-DOS style path detected: E:Workspace37NativeExample
    Preferred POSIX equivalent is: /cygdrive/e/Workspace37/NativeExample
    CYGWIN environment variable option “nodosfilewarning” turns off this warning.
    Consult the user’s guide for more details about POSIX paths:
    http://cygwin.com/cygwin-ug-net/using.html#using-pathnames
    rm -f /cygdrive/e/Workspace37/NativeExample/libs/armeabi/lib*.so /cygdrive/e/Workspace37/NativeExample/libs/armeabi-v7a/lib*.so /cygdrive/e/Workspace37/NativeExample/libs/x86/lib*.so
    rm -f /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdbserver /cygdrive/e/Workspace37/NativeExample/libs/armeabi-v7a/gdbserver /cygdrive/e/Workspace37/NativeExample/libs/x86/gdbserver
    rm -f /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdb.setup /cygdrive/e/Workspace37/NativeExample/libs/armeabi-v7a/gdb.setup /cygdrive/e/Workspace37/NativeExample/libs/x86/gdb.setup
    Gdbserver : [arm-eabi-4.4.0] /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdbserver
    mkdir -p /cygdrive/e/Workspace37/NativeExample/libs/armeabi
    install -p /cygdrive/d/android-ndk-r4-crystax/build/prebuilt/windows/arm-eabi-4.4.0/bin//gdbserver /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdbserver
    Gdbsetup : /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdb.setup
    mkdir -p /cygdrive/e/Workspace37/NativeExample/libs/armeabi
    echo “set solib-search-path /cygdrive/e/Workspace37/NativeExample/obj/local/armeabi” > /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdb.setup
    echo “directory /cygdrive/d/android-ndk-r4-crystax/build/platforms/android-8/arch-arm/usr/include” >> /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdb.setup
    Gdbsetup : + source directory /cygdrive/e/Workspace37/NativeExample/jni
    echo “directory /cygdrive/e/Workspace37/NativeExample/jni” >> /cygdrive/e/Workspace37/NativeExample/libs/armeabi/gdb.setup
    Install : libNativeExample.so => /cygdrive/e/Workspace37/NativeExample/libs/armeabi
    mkdir -p /cygdrive/e/Workspace37/NativeExample/libs/armeabi
    install -p /cygdrive/e/Workspace37/NativeExample/obj/local/armeabi/libNativeExample.so /cygdrive/e/Workspace37/NativeExample/libs/armeabi/libNativeExample.so
    /cygdrive/d/android-ndk-r4-crystax/build/prebuilt/windows/arm-eabi-4.4.0/bin/arm-eabi-strip –strip-debug /cygdrive/e/Workspace37/NativeExample/libs/armeabi/libNativeExample.so
    /cygdrive/d/android-ndk-r4-crystax/build/prebuilt/windows/arm-eabi-4.4.0/bin/arm-eabi-strip: ‘/cygdrive/e/Workspace37/NativeExample/libs/armeabi/libNativeExample.so': No such file
    make: *** [/cygdrive/e/Workspace37/NativeExample/libs/armeabi/libNativeExample.so] Error 1

    **** Build Finished ****

    I observed that the Incredibuild installer is popping up when the “install -p” command is being executed.

    Can you please guide me on what is going wrong?

    Sudhaker

    • Ivan Leben

      Regarding the IncrediBuild installer that pops up: the same thing was happening to me, then I realised I had the IncrediBuild installation folder in my windows PATH environment variable. Inside that folder there is a Install.exe. It just so happens that the ndk-build system has its own command “install” that it calls to copy the output library file to another folder. If your IncrediBuild folder is in front of the ndk (or cygwin) folder in your PATH variable, this will triger the IncrediBuild’s installation instead of what it is actually trying to do.

      Long story short: make sure your NDK and Cygwin folders are in front of the IncrediBuild folder in your Windows PATH environment variable.

  • BlogGuest

    The instructions “Now you need to add the native support for the newly created project. Just right click on the project root element in the package explorer and select ‘add native support’ ” are wrong. There is no such menu off the project root. You need to select the “Android Tools” menu.

  • Anatoly Korniltsev

    went throug all the steps and it seems like everything is ok, bu breakpoints does not work…

    i get warning: shared library handler failed to enable breakpoint
    No symbol table is loaded. Use the “file” command.

    can u help me?

  • Pingback: Any simple or easy way to debug Android NDK code? - Android Questions - Developers Q & A