2012년 3월 14일 수요일

Building/Debugging android native C applications

출처 : http://betelco.blogspot.com/2010/01/buildingdebugging-android-native-c.html


Building/Debugging android native C applications

In this post I will explain how to compile, install and debug an Android native "C" application.
If you are reading this post just because you have googled the magic keywords ("android" + "native code") then you should know that there is an easier way to build native applications using android makefiles ("Android.mk" and "Application.mk").
The method I'm describing here is only useful if you want to understand how things work in order to create more complex standardGNU makefiles. This is also useful if you would like to create your own GNU autotools wrappers to compile projects using GNU configure.
I'm using Windows Vista as host machine but any other supported platforms (e.g. linux-x86 or darwin-x86) should work.

I have tested both the NDK (1.6) and SDK (2.1) on:
  • Windows XP (32-bit) and Vista (64-bit)
  • Mac OS X Snow Leopard
  • Ubuntu Intrepid
Installing Android SDK

To download the latest Android SDK, visit this address http://developer.android.com/sdk/index.html.
If you need information on how to install the SDK, visit this address http://developer.android.com/sdk/installing.html.
If the "SDK setup" fail to update the installed packages you can change the remote site URL from https://dl-ssl.google.com/android/repository/repository.xml to http://dl-ssl.google.com/android/repository/repository.xml (change the URL scheme from HTTPS to HTTP) or try to disable your anti-virus or firewall.

I have installed the SDK version 2.1 under c:/android-sdk (a.r.a /cygdrive/c/android-sdk).
Add an environment variable named ANDROID_SDK_ROOT pointing to the SDK root directory.

Important: You should add "$ANDROID_SDK_ROOT/tools" directory to the $PATH environment variable.
Under *nix:

export PATH=$ANDROID_SDK_ROOT/tools:$PATH
Under Cygwin: Open C:\Cygwin\Cygwin.bat and add:

set PATH=%ANDROID_SDK_TOOLS%;%PATH%
Installing Cygwin

If you are using Windows XP or Vista as host machine then you MUST install Cygwin Devel package with GNU Make (3.81 or later) before installing the NDK.
It should also work with MinGW.

Installing the Android NDK

To download the latest Android NDK, visit this address http://developer.android.com/sdk/ndk/1.6_r1/index.html.
I have uncompressed the NDK version 1.6 under c:/android-ndk (a.r.a /cygdrive/c/android-ndk).
Add an environment variable named ANDROID_NDK_ROOT pointing to the NDK root directory.
To install the NDK:

cd $ANDROID_NDK_ROOT
build/host-setup.sh
If all is OK then the console will print Host setup complete.
To test that the toolchain has been correctly installed you can try to build the hello-jni sample which comes with the NDK by doing this:

cd $ANDROID_NDK_ROOT
make -APP=hello-jni
If all is OK then the console will print:

Android NDK: Building for application 'hello-jni'
Compile thumb : hello-jni <= sources/samples/hello-jni/hello-jni.c SharedLibrary : libhello-jni.so Install : libhello-jni.so => apps/hello-jni/project/libs/armeabi
This mean that your native shared library (libhello-jni.so) have been successfully generated under$ANDROID_NDK_ROOT/apps/hello-jni/project/libs/armeabi folder.

Creating an AVD

AVD stands for Android Virtual Device and can be seen as a device profile (keyboard, dialing pad, skin, screen dimensions, appearance ...) to load into your emulator. You can create as many AVDs as you need.
To create an AVD named "avdtest" targeting platform 2.1 (targetID=android-7):

android create avd -n avdtest -t android-7
If all is OK the console will print:

Created AVD 'avdtest' based on Android 2.1, with the following hardware config: hw.lcd.density=160
Create test.c

Here I will create a basic test.c file under C:\tmp with the following content:

#include <stdio.h>// printf

int main(int argc, char **argv)
{
int i = 1;
i+=2;

printf("Hello, world (i=%d)!\n", i);

return 0;
}
Create makefile

Just create an empty file named makefile (without any extension) under C:\tmp (which is the same directory as test.c).
Now We will fill the makefile step by step.
Add application name, $ROOT directory, install directory and the NDK platform version:

APP := test
ROOT:=/cygdrive/c
NDK_PLATFORM_VER := 1.5
INSTALL_DIR := /data/tmp
Add useful environment vars:

ANDROID_NDK_ROOT:=$(ROOT)/android-ndk
ANDROID_NDK_HOST:=windows
ANDROID_SDK_ROOT:=$(ROOT)/android-sdk
PREBUILD:=$(ANDROID_NDK_ROOT)/build/prebuilt/$(ANDROID_NDK_HOST)/arm-eabi-4.2.1
BIN := $(PREBUILD)/bin
You MUST change ANDROID_NDK_HOST value from windows to linux-x86 if you are under *nix or darwin-x86 on MAC OS X.
Add GCC options:

CPP := $(BIN)/arm-eabi-g++
CC := $(BIN)/arm-eabi-gcc
CFLAGS :=
LDFLAGS := -Wl
Add targets

all: $(APP)
OBJS += $(APP).o
$(APP): $(OBJS)
$(CPP) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) -c $(INCLUDE) $(CFLAGS) $< -o $@
install: $(APP)
$(ANDROID_SDK_ROOT)/tools/adb push $(APP) $(INSTALL_DIR)/$(APP)
$(ANDROID_SDK_ROOT)/tools/adb shell chmod 777 $(INSTALL_DIR)/$(APP)
shell:
$(ANDROID_SDK_ROOT)/tools/adb shell
run:
$(ANDROID_SDK_ROOT)/tools/adb shell $(INSTALL_DIR)/$(APP)
clean:
@rm -f $(APP).o $(APP)
Building the application
To build the application, switch to the directory where you have created both files and then:

make
At the output of the console you will get many errors saying that it's impossible to find stdlib.h, stdio.h etc etc.To resolve this issue, add the Bionic header files to $CFLAGS variable like this:

CFLAGS := -I$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/include
If you retry (make) you will now get this link error:

crt0.o: No such file: No such file or directory
To avoid directly linking against the "C runtime" you must add "-nostdlib" flag to the link options like this:

LDFLAGS := -Wl -nostdlib
If you retry (make) you will now get these link errors:

test.c:(.text+0x34): undefined reference to `printf'
test.c:(.text+0x3c): undefined reference to `exit'
You get these errors because Bionic libc is missing. To add libc you MUST change $LDFLAGS like this:

LDFLAGS := -Wl -L$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib
LDFLAGS += -nostdlib -lc

If you retry (make) you will now get this link error:

/cygdrive/c/android-ndk/build/platforms/android-1.5/arch-arm/usr/lib/libc.so: undefined reference to `dl_unwind_find_exidx'
To resolve this issue you MUST specify the first set of directories into which to search the system shared libraries (*.so) . This is done by adding the "-rpath-link" option to the link options like this:

LDFLAGS := -Wl,-rpath-link=$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib -L$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib
If you retry (make) you will now get this warning:

/cygdrive/c/android-ndk/build/prebuilt/windows/arm-eabi-4.2.1/bin/../lib/gcc/arm
-eabi/4.2.1/../../../../arm-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 000082c8
This is an Android known issue. You have this warning because the linker search "_start" as entry point. You can resolve this issue by renaming your main function. But the elegant way to resolve this issue is to specify the entry point in the link options like this:

LDFLAGS := -Wl,--entry=main,-rpath-link=$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib -L$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib
LDFLAGS += -nostdlib -lc
Now When you retry (make) your application will successfully build without any errors or warnings.

Testing your application

Before testing your application you MUST run the emulator like this:

emulator -avd avdtest
where "avdtest" is the name of the previously created avd (see "creating an avd" section).
To install the application on the emulator, open a new console and go to to directory where you have created test.c and makefile. Install your application on the emulator like this:

make install
If all is OK the console will print:

/cygdrive/c/android-sdk/tools/adb push test /data/tmp/test
304 KB/s (2493 bytes in 0.008s)
/cygdrive/c/android-sdk/tools/adb shell chmod 777 /data/tmp/test
To run the application type:

make run
You will probably get an error message saying:

/cygdrive/c/android-sdk/tools/adb shell /data/tmp/test
/data/tmp/test: not found
This error message is a bit confusing because if you browse the /data/tmp directory you will notice that the executable is here. The question is why?
I spent hours searching and I found that this error happens because the loader fails to load the application because it cannot found a proper linker.
To specify a search directory for the dynamic linker (at run time) you MUST change the link options like this:

LDFLAGS := -Wl,--entry=main,-rpath-link=$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib,-dynamic-linker=/system/bin/linker -L$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib
LDFLAGS += -nostdlib -lc
Now rebuild and install your application (make clean && make && make install) then run it again (make run).
The console will print the expected result ("hello, world (i=3)!") but just after we have an segmentation fault error ("[1] Segmentation fault /data/tmp/test").
To resolve this issue you can exit the program (exit(0);) just before the main function returns (return 0;). You should alsoinclude <stdlib.h>.
If you retry the build&&run process (make clean && make && make install && make run) then you should have:

/cygdrive/c/android-sdk/tools/adb shell /data/tmp/test
Hello, world (i=3)!
which is the expected result.

Debugging your applicationBefore doing anything you MUST copy the gdbserver file to the emultor.
This file is under $BIN ($ANDROID_NDK_ROOT/build/prebuilt/$ANDROID_NDK_HOST/arm-eabi-4.2.1/bin).Copy gdbserver to the emulator like this:

adb push gdbserver $INSTALL_DIR/gdbserver
adb shell chmod 777 $INSTALL_DIR/gdbserver
where $INSTALL_DIR is the directory where you have installed your application (it's not mandatory to copy it in this directory).
Before running the server on port 1234 you MUST redirect all tcp connection to this port like this:

adb forward tcp:1234: tcp:1234
it's not mandatory to forward connections to the same port number.
Now it's time to run the server:

adb shell $INSTALL_DIR/gdbserver :1234 $INSTALL_DIR/$APP
note that only the server port is specified (no host).
If all is OK the the server will print something like this:

Process /data/tmp/test created; pid = 246
Listening on port 1234
Now to debug our application we will change the makefile by adding a new debug target like this.

GDB_CLIENT := $(BIN)/arm-eabi-gdb

debug:
$(GDB_CLIENT) $(APP)

To launch the application in debug mode type "make debug" (after make clean && make && make install of course). If you do this, you will see a warning message saying that "no debugging symbols found". No symbols ==> no debug.
To generate debug symbols you MUST change the makefile like this (should not be hard coded like this):

DEBUG = -g
CFLAGS := $(DEBUG) -I$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/include
Now rebuild and install your application (make clean && make && make install) then run it again (make debug). This (make debug) should open gdb invite command((gdb)) on the same console.
Connect to the server (from the same console) like this:

target remote :1234
Set a breakpoint on the main function and execute step by step (commands above are informational and you can use any gdb commands):

b main
c
n
p i 
#$1 = 1
n
#9 printf("Hello, world (i=%d)!\n", i);
p i 
#$2 = 3
c
#Program exited normally.

The final makefile and test.c files are shown below:

makefile

APP := test
ROOT:=/cygdrive/c
INSTALL_DIR := /data/tmp
NDK_PLATFORM_VER := 1.5

ANDROID_NDK_ROOT:=$(ROOT)/android-ndk
ANDROID_NDK_HOST:=windows
ANDROID_SDK_ROOT:=$(ROOT)/android-sdk
PREBUILD:=$(ANDROID_NDK_ROOT)/build/prebuilt/$(ANDROID_NDK_HOST)/arm-eabi-4.2.1
BIN := $(PREBUILD)/bin
GDB_CLIENT := $(BIN)/arm-eabi-gdb

DEBUG = -g

CPP := $(BIN)/arm-eabi-g++
CC := $(BIN)/arm-eabi-gcc
CFLAGS := $(DEBUG) -I$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/include
LDFLAGS := -Wl,--entry=main,-rpath-link=$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib,-dynamic-linker=/system/bin/linker -L$(ANDROID_NDK_ROOT)/build/platforms/android-$(NDK_PLATFORM_VER)/arch-arm/usr/lib
LDFLAGS += -nostdlib -lc

all: $(APP)

OBJS += $(APP).o
$(APP): $(OBJS)
$(CPP) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) -c $(INCLUDE) $(CFLAGS) $< -o $@
install: $(APP)
$(ANDROID_SDK_ROOT)/tools/adb push $(APP) $(INSTALL_DIR)/$(APP)
$(ANDROID_SDK_ROOT)/tools/adb shell chmod 777 $(INSTALL_DIR)/$(APP)
shell:
$(ANDROID_SDK_ROOT)/tools/adb shell
run:
$(ANDROID_SDK_ROOT)/tools/adb shell $(INSTALL_DIR)/$(APP)
debug:
$(GDB_CLIENT) $(APP)
clean:
@rm -f $(APP).o $(APP)

test.c

#include <stdio.h> // printf
#include <stdlib.h> //exit

int main(int argc, char **argv)
{
int i = 1;
i+=2;

printf("Hello, world (i=%d)!\n", i);

exit(0);
return 0;
}

댓글 없음:

댓글 쓰기