hello-gdbserver: A debuggable JNI example for Android

Prolog

Before I get into the rest of the post, here is a link to a .zip of the Eclipse project files if you just want to skip ahead.

The Rest Of The Post

Native software development on Android using its JNI capabilities is incredibly frustrating without proper visibility into the causes of native code errors.

Here is an example project and some background info that shows how to get gdb and gdbserver working to catch those nasty bugs. Just extract the project files into the NDK’s sample directory and import the project into Eclipse.

Here were some of the search terms I tried unsuccessfully while trying find something like this:

using gdb on android
using ndk-gdb
debug jni android
hello world android native code
hello world android emulator
debugging using gdbserver android

Requirements

  • A phone running Android 2.2 (Froyo) (might work on the emulator, but I didn’t focus on that)
  • The latest Android NDK (at least r4b)
  • Lots of Patience

The NDK-GDB.TXT readme file in the NDK has a good overview of the process of getting a debuggable Android application + JNI build set up, but I would have much preferred it if Google had simply included something like a hello-gdbserver test app in the provided samples. One simple app where you could induce a crash to see what would happen, like a tutorial for smarter bug-hunting. So, I made this one.

Getting Started

Here’s an example of some (exaggeratedly) buggy code in the JNI shared object in jni/hello-gdbserver.c:

Fugly. This will kill your app dead without so much as a thank you or goodbye. And you’ll never know what hit you, because all you see is a stack trace in DDMS that says “I blew up, and here is a long list of hexadecimal numbers you’ll never decode.” In any non-trivial app (which is basically any app), you could spend days trying to hunt this down, which is certainly not the best use of engineer time.

The Google guys have a page describing how to transcribe that stuff back into meaningful information, but it’s long-winded, manual, and from a practical standpoint a waste of time.

The Fix

Step 1. Tell the gcc C compiler to add debug symbols to your code

In Android.mk add the following line:

Problem 1. Android Ignores You

Oddly enough, even with the debug flag set, you’ll notice that the final step of ndk-build, the Install step, copies and then strips your binaries of debug information. Not very helpful, that. Just run the ndk-build script with verbose mode active, V=1, and you’ll see something like this:

which is due to a line in the setup.mk Makefile fragment:

That last line kills your debugging symbols.

Step 2. Ignore Android


So you have to edit it out, as a quick hack, in the install-binary.mk Makefile fragment:

A better fix would be to add a real variable check in the Makefile for debug vs. non debug builds and to act accordingly. In any case, your next builds will have full debug symbols in them. So just do a quick


[update: 2010/09/24]

A better fix:

Now when you’re building, you can just specify the APP_OPTIM level as a parameter to ndk-build:

This is also useful because APP_OPTIM can be specified in the Application.mk file.

Step 3. Rebuild Your APK

Ok, so once you have the shared object built, it’s time to package it into an APK using Eclipse. This is pretty standard. Since the ndk-build has “installed” the libhello-gdbserver.so shared object to the right directory, just do a Build -> Clean, Build -> Rebuild All in Eclipse and wait the half a second it takes to regenerate the APK.

Try running the application, you should see this:

Then, still in the project’s directory, call ndk-gdb.

Fun Error Message 1

Make sure in AndroidManifest.xml, you’ve got Debuggable set to true. Rebuild and reinstall.

Fun Error Message 2

Rebuild and reinstall.

Fun Error Message 3

Sometimes the above message looks like the following, where the information gets mashed up wrongly into the verbose output from ndk-gdb. e.g. the string “run-as: Package ‘com.example.hellogdbserver’ has corrupt installation” was variable-substituted into the command to forward debug information over tcp/5039.

The solution remains the same: Rebuild APK and reinstall.

Fun Error Message 4

Sometimes you must Clean and Rebuild your entire project in Eclipse, especially if you make a dumb mistake, like change your Java Package from “com.examples.hellogdbserver” to “com.example.hellogdbserver”. Sometimes that slight change doesn’t quite make it into the package, or the aapt package manager doesn’t see it, and you end up with an application running under a package name that isn’t what you expect, and isn’t one that run-as can find. Try adb install / uninstalling a couple of times, or uninstalling via the phone’s Settings -> Applications menu to get the app into a state where the phone recognizes the package.

The rule of thumb is: If adb install/uninstall can see your program by its package name, then so can gdb.

When It Works, It Looks Like This

Tap the “Induce Crash” button. gdb should immediately produce the following:

Rejoice In No Longer Needing __android_log_print()

Hallelujah! Usable segmentation fault information. Finally, one can get rid of all those debug print statements!

Questions / Feedback / Code Fail?

File them in the comments and I’ll update the above. Thanks!

23 thoughts on “hello-gdbserver: A debuggable JNI example for Android”

  1. really thx for this! you forgot that user need type “continue” after

    “warning: shared library handler failed to enable breakpoint
    0xafd0ebd8 in ?? ()
    (gdb)”

    to continue with debug

  2. Thanks for the example, it was very useful.

    I have a strange problem though, I was always getting Fun Error Message 4 for my application, even after lots of reinstalling and messing about. Then I renamed my package to com.example.hellogdbserver (the same as your example) and then it works. Conversely, renaming your example package to anything other than com.example.hellogdbserver stops it from working.

    I’ve been scouring through the projects for hours and can’t see any settings which would account for this behaviour.

    I then tried it on another phone, going through the steps carefully and can’t get anything to run at all, not even your sample.

    On an emulator however it works straight away.

    Anyone else seen a similar problem?

    1. You might have to grep through the project directory to see where the various package names and class names are being used. I know I made a similar change (e.g. renamed a class or the project) at some point and had to do this to get it to work.

      1. I got it working on my phone in the end by renaming the project package to be an extra level deep (i.e. com.first.second.third rather than com.first.second). I tried calling run-as on various applications on my phone through adb and I still can’t find a definite pattern as to which ones work and which don’t.

  3. If the value int *crasher = 0x0; was changed to 0x1 would this

    signal 11 (SIGSEGV), fault addr 00000000

    change to

    signal 11 (SIGSEGV), fault addr 00000001

    meaning that a fault addr of 0000000 would mean someone was assigning something to NULL?

  4. Hi,

    Thanks a lot for the article ! It worked fine with your example, and I could play for a while with it.
    But during my tests, I tried using a bit of C++ (to see how class / method debugging was working with gdb)

  5. Hi again … seems my comment got html filtered :s
    I was saying that I could use gdb with your example without any problem, but as soon as I add a .cpp / .h files, gdb doesn’t work anymore.
    I don’t understand why … I copied the CFLAGS into the CPPFLAGS to make sure .cpp files were compiled with the same debug informations, but it still didn’t work …

    Any idea what I might be doing wrong ? If you need details, please drop me a mail, I’ll send your the modified example I’m using and the errors I get 🙂

    Thanks in advance !

  6. Hi ! In case someone has the same difficulty as I had, to correct the problem, I had to add

    extern “C”

    before my c function in the .cpp file to make it “visible” by gdb. Then I could debug everything, and even add other cpp files with classes, etc.

  7. In the r5b release of the NDK, the file that you do the APP_OPTIM hack on has been renamed to build/core/build-binary.mk.

    In my opinion, Google should make your APP_OPTIM hack a standard feature of the next release of the NDK!

  8. A couple things to note. ndk-gdb REALLY doesn’t like having any spaces in the adb path. Since my sdk is installed in Program Files, I had to create a symbolic link to make sure the path had no spaces in it.

    If your native library is part of an Android library that links with your application, you need to change PACKAGE_NAME in ndk-gdb (line 332 for me) to be the package name of the application (it tries to use the package of the Android library that owns the native code). Otherwise it won’t be able to find and attach to your application.

  9. Great tutorial! Just a note to add. If you are using Android Libraries that link to your main application and your JNI code is in the library, you will run into naming problem where when you execute ndk-gdb you will get the error that the application has not been started even when it is running. You will need to edit ndk-gdb. Find the line:
    PACKAGE_NAME=run_awk_manifest_script extract-package-name.awk
    and change it to:
    PACKAGE_NAME=”complete namespace of your application”
    Then it will see that your app is running!

  10. In Android 2.2 it’s possible to debug only main thread. Multithreading gdb support is avaiable from 2.3(see ndk docs)
    Also for debugging it’s better to use Sequoyah plugin for eclipse – it allows you to convinient interact with gdbserver, debugging native code similar to java code.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.