HOWTO: Android NDK debugging with Insight GDB

December 26, 2011 —

This is a "How To" tutorial based on my experience building and configuring the Insight GDB GUI for use with the Android NDK. I had a post explaining how to get it all set up on Linux on the old version of this blog, but that post was lost when I switched to using WordPress.

There appears to be very little information on this out there, with most search results for "ndk gdb gui" linking to people wanting to use Eclipse with GDB. I can't be bothered to learn how to use GDB directly from the command line and like to have a nice GUI interface to it and I despise Eclipse. I don't actually need to do much on-device debugging with Android as my code framework runs desktop builds also and so I can debug almost everything using a desktop build which is much easier since most IDE's have good GDB integration for that kind of thing. However, once in a while it is very handy to be able to do on-device debugging.

So here it is again, but this time with an extra bonus: how to set it up on Mac OS X.

Building Insight on Linux

Building Insight for an ARM target on Linux is actually quite easy:

$ cd
$ wget ftp://sourceware.org/pub/insight/releases/insight-6.8-1a.tar.bz2
$ mkdir -p ~/sources/insight
$ tar xjf insight-6.8-1a.tar.bz2 -C ~/sources/insight/
$ mkdir ~/sources/insight/build
$ cd ~/sources/insight/build
$ ~/sources/insight/insight-6.8-1/configure \
        --target=arm-elf-linux \
        --disable-werror \
        --prefix=/usr/local/insight-arm-elf-linux \
        --mandir=/usr/local/insight-arm-elf-linux/man \
        --infodir=/usr/local/insight-arm-elf-linux/share/info
$ make
$ sudo make install

After which all of Insight's files will be located under /usr/local/insight-arm-elf-linux so that it?s nicely separated from any other Insight installation that you might already have installed.

Building Insight on OS X

If we were to just follow the above procedure for building Insight on Linux then we would run into issues during the build. Out of the box, the build scripts won't be able to locate the X11 libraries on OS X. I spent a bit of time fiddling with this after not being able to find any info about it, and have devised a simple work around that requires editing a couple files before building. I suspect that this is not the "proper" way to fix this and that there's a better way, but even still, this isn't too bad. And it works.

I assume that you have X11 installed already, as it is required.

$ cd
$ curl -O ftp://sourceware.org/pub/insight/releases/insight-6.8-1a.tar.bz2
$ mkdir -p ~/sources/insight
$ tar xjf insight-6.8-1a.tar.bz2 -C ~/sources/insight/

At this point, we need to edit a couple files to make sure that the build process will find the X11 libraries which are located in /usr/X11/lib.

$ nano ~/sources/insight/insight-6.8-1/tk/unix/Makefile.in

Now go to line 133 (at least in the 6.8-1a sources) and change as follows:

# Linker switch(es) to use to link with the X11 library archive (the
# configure script will try to set this value automatically, but you
# can override it).
X11_LIB_SWITCHES = -L/usr/X11/lib @XLIBSW@

# To turn off the security checks that disallow incoming sends when

Next:

$ nano -w ~/sources/insight/insight-6.8-1/gdb/Makefile.in

Then go to line 289 (again, at least in the 6.8-1a sources) and change like so:

ITK = @ITKLIB@

X11_CFLAGS = @TK_XINCLUDES@
X11_LDFLAGS = -L/usr/X11/lib
X11_LIBS =

WIN32LDAPP = @WIN32LDAPP@

Now we can continue building Insight:

$ mkdir ~/sources/insight/build
$ cd ~/sources/insight/build
$ ~/sources/insight/insight-6.8-1/configure \
        --target=arm-elf-linux \
        --disable-werror \
        --prefix=/usr/local/insight-arm-elf-linux \
        --mandir=/usr/local/insight-arm-elf-linux/man \
        --infodir=/usr/local/insight-arm-elf-linux/share/info
$ make
$ make install

Now Insight will be located under /usr/local/insight-arm-elf-linux.

There is only one issue so far that I?ve run into with Insight on OS X. If you run Insight and then choose Preferences > Global from the menu, Insight will crash with the error:

X Error of failed request:  BadValue (integer parameter out of range for operation)
  Major opcode of failed request:  45 (X_OpenFont)
  Value in failed request:  0x60014a
  Serial number of failed request:  2616
  Current serial number in output stream:  2617

I don't have a solution for this, as I don't yet need to change anything in that section of the preferences yet, so I haven't tried to fix the problem yet. You're on your own there!

Configuring ndk-gdb to use Insight

By default, the ndk-gdb command that comes with the Android NDK uses the regular old command line GDB. ndk-gdb is just a shell script which we can edit easily enough and point it to our freshly built Insight binary.

Open up the ndk-gdb script in a text editor. This is located in the root of your Android NDK installation. Find line 632 or so and change as follows:

# Now launch the appropriate gdb client with the right init commands
#
GDBCLIENT=/usr/local/insight-arm-elf-linux/bin/arm-elf-linux-insight
GDBSETUP=$APP_OUT/gdb.setup
cp -f $GDBSETUP_INIT $GDBSETUP

Now when you run ndk-gdb, Insight will be used giving you an ugly GUI interface to GDB.

Using Insight to debug Android NDK code on Linux

Using Insight to debug Android NDK code on OS X

Extras

One annoying thing about using ndk-gdb is that it starts the app on your Android device and then attaches to the gdbserver that gets deployed alongside your app. This process takes several seconds usually, and until GDB attaches, your app will continue running like normal which is a problem if you actually need to debug some issue that occurs during your app's startup (before GDB is able to attach). The only solution that I've found for this is to insert a delay as soon as possible in your code (e.g. right in your android_main() function). I've found a 10 second delay to be more then adequate for all the devices I have (Nexus One, Nexus S, Xperia Play, Xoom, Galaxy Nexus). I use the following functions to implement a delay:

#include <time.h>
#include <stdint.h>

uint32_t GetTicks() const
{
    timespec time;
    clock_gettime(CLOCK_MONOTONIC, &time);
    return ((time.tv_sec * 1000) + (time.tv_nsec / 1000000));
}

void Delay(uint32_t milliseconds) const
{
    unsigned int start = GetTicks();
    unsigned int elapsed = 0;
 
    do
    {
        elapsed = GetTicks() - start;
    }
    while (milliseconds > elapsed);
}

Then you can just use the following to delay for 10 seconds:

Delay(10000);

Also, you can trigger breakpoints through code which Insight will stop at when execution reaches that point (just as if you'd added a breakpoint through Insight itself). This can be handy in an assert macro or similar:

#define BREAKPOINT __asm__ ("bkpt 0")

Of course, that's for ARM devices only. This is maybe more future proof with x86 Android devices on the way in 2012:

#if (defined(__arm__) || defined(__thumb__))
    #define BREAKPOINT __asm__ ("bkpt 0")
#else
    #define BREAKPOINT __asm__ ("int $3")
#endif