출처 :
http://www.aton.com/android-native-development-using-the-android-open-source-project/
BY CHARLES WILDE AT JULY 31, 2009 4:21 PM
Part Four of a Series of Posts about Android Mobile Application Development.
Tools for Native Code Development
There are now two primary sets of tools that can be used to develop Android applications that include native C/C++ native code. The original, and still unsupported way, is to leverage the tools built into the Android Open Source Project to build Android applications. The intent of the Android Open Source Project is to provide the means to port the Android operating system to hardware devices, ranging from cell phones, to netbooks, to set top boxes, etc.
Developing Android applications that include native code by using the Android Open Source Project allows the developer full access to every aspect of the operating system. Because the operating system is currently under development, the Android team will be changing various aspects of the operating system source. If you develop an Android application that happens to utilize one of the features that have changed, then your application may very well break when run on a device with a newer release of Android.
For this reason, the Android team has recently released the Android NDK or Android Native Development Kit. The goal of the NDK is to allow native code libraries to be incorporated into an Android Java application. A secondary goal is to limit access of native code to selected API’s and libraries that are considered mature, and thus are guaranteed to be supported in future releases of the Android OS. The Android NDK is available here:
http://developer.android.com/sdk/ndk/1.5_r1/index.html.
In this post, I will be working with the Android Open Source Project (OSP) to build Android applications that utilize native code. In a later post, I will address the NDK, with its advantages and limitations. Using the OSP is useful where you are building applications for a custom port of Android. It is also helpful if you need access to API’s and libraries that are not included in the current release of the NDK and you are willing to accept that those API’s and libraries may change in future releases of the Android OS.
A Simple, Pure Native Code Application for Android
As our first example, I will build a non-trivial, but still simple C language application that does not involve Java. I will do this by cloning the classic “ping” application used to test TCP/IP connectivity in Android.
It is important to note that using the OSP for native development requires that I respect the file structure of the OSP. To do otherwise can generate a prohibitive amount of work to get the build system to perform properly.
In this discussion I will be referring to the environment variable $ANDROID_HOME, which is based on the instructions I presented in previous posts is equivalent to $HOME/mydroid. The descriptions below are for Ubuntu 9.04.
Applications that support the OS, such as “ping” reside in the folder $ANDROID_HOME/external. To avoid collisions with the rest of the OSP, I will create a folder for our native applications at $ANDROID_HOME/external/myapps.
The first step is to copy the folder and contents $ANDROID_HOME/external/ping to $ANDROID_HOME/external/myapps/ping. Then, to identify our modified code and to avoid overwriting the original ping command files, rename the folder $ANDROID_HOME/external/myapps/ping to $ANDROID_HOME/external/myapps/myping. In this folder, rename ping.c to my_ping.c and ping_common.h to my_ping_common.h. Use a text editor to change ping_common.h to my_ping_common.h in the #include statement in “my_ping.c”. You should also change “ping” to “my_ping” in the text prompt and error message strings to help identify the output as actually coming from this modified application.
Finally I need to adjust the Android.mk file in the folder $ANDROID_HOME/external/myapps/myping to reflect our other changes here. Note that this file is quite compact, reflecting only differences from the standard make procedures buried in the Android OS build system. The build system is very powerful and elegant, but quite complex if you choose to burrow into the details of how it actually works.
In the Android.mk file, make the following edits.
Change the line LOCAL_SRC_FILES:= ping.c to LOCAL_SRC_FILES:= my_ping.c.
Change the line LOCAL_MODULE := ping to LOCAL_MODULE := my_ping.
Note that the existing Android.mk file specifies static libraries with this line:
LOCAL_STATIC_LIBRARIES := libcutils libc
Next, open a terminal window and change the directory to $ANDROID_HOME/external/myapps/myping.
To setup the build environment use this command: source $ANDROID_HOME/build/envsetup.sh
Then enter the command “mm”. This simple command initiates a complex set of events that will build the application “my_ping” in about a minute. The terminal session output will look something like this:
cawilde@android:~$ cd $ANDROID_HOME/external/myapps/myping
cawilde@android:~/mydroid/external/myapps/myping$ source $ANDROID_HOME/build/envsetup.sh
cawilde@android:~/mydroid/external/myapps/myping$ mm
make: Entering directory `/home/cawilde/mydroid'
build/core/product_config.mk:261: WARNING: adding test OTA key
============================================
TARGET_PRODUCT=generic
TARGET_BUILD_VARIANT=eng
TARGET_SIMULATOR=
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm
HOST_ARCH=x86
HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=
============================================
target thumb C: my_ping <= /home/cawilde/mydroid/external/myapps/myping/my_ping.c
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function 'fill':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:136: warning: pointer targets in initialization differ in signedness
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function '__schedule_exit':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:315: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:320: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function 'setup':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:533: warning: pointer targets in initialization differ in signedness
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function 'gather_statistics':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:725: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: At top level:
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:958: warning: missing initializer
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:958: warning: (near initialization for 'cmsg.ipi.ipi_spec_dst')
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function 'receive_error_msg':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:1017: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function 'parse_reply':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:1179: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:1183: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c: In function 'main':
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:2038: warning: comparison betIen signed and unsigned
/home/cawilde/mydroid/external/myapps/myping/my_ping.c:2046: warning: comparison betIen signed and unsigned
target Executable: my_ping (out/target/product/generic/obj/EXECUTABLES/my_ping_intermediates/LINKED/my_ping)
target Non-prelinked: my_ping (out/target/product/generic/symbols/system/bin/my_ping)
target Strip: my_ping (out/target/product/generic/obj/EXECUTABLES/my_ping_intermediates/my_ping)
Install: out/target/product/generic/system/bin/my_ping
Finding NOTICE files: out/target/product/generic/obj/NOTICE_FILES/hash-timestamp
Combining NOTICE files: out/target/product/generic/obj/NOTICE.html
gzip -c out/target/product/generic/obj/NOTICE.html > out/target/product/generic/obj/NOTICE.html.gz
make: Leaving directory `/home/cawilde/mydroid'
cawilde@android:~/mydroid/external/myapps/myping$
The build lists a number of warnings related to signed/unsigned variable mismatches which exist in the source file. These can be ignored for purposes of this demonstration. The build listing also describes where the various types of build outputs are located. These are close to the end of the session:
target Executable: my_ping (out/target/product/generic/obj/EXECUTABLES/my_ping_intermediates/LINKED/my_ping) (61 KB)
target Non-prelinked: my_ping (out/target/product/generic/symbols/system/bin/my_ping) (61 KB)
target Strip: my_ping (out/target/product/generic/obj/EXECUTABLES/my_ping_intermediates/my_ping) (27 KB)
Install: out/target/product/generic/system/bin/my_ping (27 KB)
The build outputs of interest are described by the lines:
Install: out/target/product/generic/system/bin/my_ping (no symbols)
target Executable: out/target/product/generic/obj/EXECUTABLES/my_ping_intermediates/LINKED/my_ping (includes debug symbols)
These lines mean the build outputs are located at $ANDROID_HOME at the paths given above.
Start a new terminal window and point it to the Android SDK development tools directory, located where you installed it. For example:
$HOME/android-sdk-linux_x86-1.5_r2/tools.
You may have a different location or SDK revision number.
You can then start the emulator with this command: emulator -avd <avd_name> where avd_name is the name you gave the AVD when you created it, and is listed by the command: “android list avds”. Note that the “.avd” extension is not included as part of <avd_name>.
Once the Android emulator has booted up, you can try out your new my_ping application using the following procedure:
- Start up a new terminal window. Start a remote shell session using the following command:
adb shell.
- This will reflect the input/output from a shell running as root in the emulator into the workstation terminal session window.
- Create a temporary folder in the /data folder on the emulator:
mkdir /data/tmp
- Then push a copy of your my_ping executable to the temporary directory using the following command in another terminal session:
adb push $ANDROID_HOME/out/target/product/generic/system/bin/my_ping /data/tmp.
- The push command responds with this type of output:
654 KB/s (26628 bytes in 0.039s).
- Finally, you can execute the code (my_ping localhost) using the remote shell session you previously used to create the temporary data folder as follows:
# /data/tmp/my_ping localhost
After a few pings on localhost are executed, type control C into the shell session to terminate the ping command. The output of my_ping in the shell session should look like this:
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=1.79 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.166 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.153 ms
64 bytes from localhost (127.0.0.1): icmp_seq=4 ttl=64 time=0.151 ms
^C
--- localhost my_ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3026
msrtt min/avg/max/mdev = 0.151/0.566/1.794/0.709 ms
If you want to make a change to Android.mk, for example to change a compilation or link option, you need to use the “touch my_ping.c” command to cause the rebuild to occur. If I do not touch a source file, the build system thinks nothing is to be done, even though I modified the Android.mk file.
So, how do you install a pure native application into the Android phone?
If you are a device manufacturer, you can build it into the full Android image for your device. Otherwise, you need a Java application as a wrapper. The Java wrapper application will provide the AndroidManifest.xml file and META-INF folder required by the Android package installer. There are other good reasons to use a Java wrapper, including access to framework libraries and easy design of GUI elements.
The use of a pure native application is also useful for debugging native shared libraries used with Java applications. I will describe this in a later post.
Android Native Development Debugging Techniques
The next step is to debug your native application. I do not have the assistance of the Eclipse CDT GUI here, so I need to work with the classic GDB command line debug tool. I could build and load a version of GDB directly onto the Android platform and operate it through a terminal window, but this approach has problems. A major issue is building and debugging GDB itself on the Android platform. GDB is a fairly large program and resources on Android are limited. So, a better approach is to use a remote debugging strategy.
Because I am debugging on a different platform from our workstation I need a tool on the Android platform that will interact with the GDB debugger running on the workstation. This tool is named gdbserver, and is included in the Android 1.5 emulator in the folder “/system/bin”.
The setup and use of gdb with gdbserver is a bit complex. There is a function named gdbclient in the shell library $ANDROID_HOME/build/envsetup.sh which is used to startup both gdbserver and then gdb itself. I have adapted the gdbclient function into a shell script used for debugging our my_ping application instead of its original purpose of debugging the Android kernel code. This adaptation is covered by the Android license (Apache) as shown here:
http://www.apache.org/licenses/LICENSE-2.0.
Create a shell file named “debug” in the myping folder that contains this text:
source $ANDROID_HOME/build/envsetup.sh
setpaths
OUT_ROOT=$ANDROID_HOME/$(get_build_var PRODUCT_OUT)
echo “OUT_ROOT = $OUT_ROOT”
OUT_SYMBOLS=$ANDROID_HOME/$(get_build_var TARGET_OUT_UNSTRIPPED)
echo “OUT_SYMBOLS = $OUT_SYMBOLS”
OUT_SO_SYMBOLS=$ANDROID_HOME/$(get_build_var TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED)
echo “OUT_SO_SYMBOLS = $OUT_SO_SYMBOLS”
OUT_EXE_SYMBOLS=$ANDROID_HOME/$(get_build_var TARGET_OUT_EXECUTABLES_UNSTRIPPED)
echo “OUT_EXE_SYMBOLS = $OUT_EXE_SYMBOLS”
PREBUILTS=$ANDROID_HOME/$(get_build_var ANDROID_PREBUILTS)
echo “PREBUILTS = $PREBUILTS”
LOCAL_EXE=”$1″
if [ -z "$LOCAL_EXE" ] ; then
echo “usage: debug local_exe remote_exe arguments”
exit
fi
REM_EXE=”$2″
if [ -z "$REM_EXE" ] ; then
echo “usage: debug local_exe remote_exe arguments”
exit
fi
ARG_LIST=”$3″
if [ -z "$ARG_LIST" ] ; then
echo “usage: debug local_exe remote_exe arguments”
exit
fi
PORT=”:5039″
adb forward “tcp$PORT” “tcp$PORT”
echo “PORT = $PORT, LOCAL_EXE = $LOCAL_EXE, REM_EXE = $REM_EXE, ARG_LIST = $ARG_LIST”
adb shell gdbserver $PORT $REM_EXE $ARG_LIST &
sleep 2
echo >|”$OUT_ROOT/gdbclient.cmds” “set solib-absolute-prefix $OUT_SYMBOLS”
echo >>”$OUT_ROOT/gdbclient.cmds” “set solib-search-path $OUT_SO_SYMBOLS”
echo >>”$OUT_ROOT/gdbclient.cmds” “target remote $PORT”
echo >>”$OUT_ROOT/gdbclient.cmds” “”
arm-eabi-gdb -silent -x “$OUT_ROOT/gdbclient.cmds” “$OUT_EXE_SYMBOLS/$LOCAL_EXE”
To use this for debugging “my_ping” you will need to recompile my_ping using the appropriate debugger options. Add this line to Android.mk immediately after the “LOCAL_SRC_FILES:= my_ping.c” line:
LOCAL_CFLAGS:=-O0 –g
The –O0 option disables GCC compiler optimizations and the –g option outputs additional debug information into the linked output. Without these two options, the GDB debugger operation can be very erratic. As mentioned above, you need to use the “touch my_ping.c” command to cause the rebuild to occur. If I do not touch a source file, the build system thinks nothing is to be done, even though I modified the Android.mk file. The rebuilt outputs are somewhat bigger, reflecting the reduced optimization and additional debug information.
Next, start up the emulator as described
above.
You can then start the emulator with this command: emulator -avd <avd_name> where avd_name is the name you gave the AVD when you created it, and is listed by the command: “android list avds”. Note that the “.avd” extension is not included as part of <avd_name>.
With the emulator started, you can copy the build outputs to the correct locations. Use this command to copy the “symbols stripped” output to the emulator:
adb push $ANDROID_HOME/out/target/product/generic/system/bin/my_ping /data/tmp
Then, use this command to copy the output with symbols to the local folder for use by the GDB debugger:
cp $ANDROID_HOME/out/target/product/generic/obj/EXECUTABLES/my_ping_intermediates/LINKED/my_ping ./
Now you are ready to initiate a debug session using GDB on your workstation communicating with gdbserver on the Android emulator:
./debug my_ping /data/tmp/my_ping localhost
The initial output of the debug session looks like this:
OUT_ROOT = /home/cawilde/mydroid/out/target/product/generic
OUT_SYMBOLS = /home/cawilde/mydroid/out/target/product/generic/symbols
OUT_SO_SYMBOLS = /home/cawilde/mydroid/out/target/product/generic/symbols/system/lib
OUT_EXE_SYMBOLS = /home/cawilde/mydroid/out/target/product/generic/symbols/system/bin
PREBUILTS = /home/cawilde/mydroid/prebuilt/linux-x86
PORT = :5039, LOCAL_EXE = my_ping, REM_EXE = /data/tmp/my_ping, ARG_LIST = localhost
Process /data/tmp/my_ping created; pid = 695
Listening on port 5039
Remote debugging from host 127.0.0.1
gdb: Unable to get location for thread creation breakpoint: requested event is not supported
__dl__start () at bionic/linker/arch/arm/begin.S:35
35 mov r0, sp
gdb: Unable to get location for thread creation breakpoint: requested event is not supported
Current language: auto; currently asm
(gdb)
The debug session starts the Android loader for /data/tmp/my_ping and the initial debug location is in the Android loader, not our application. The loader is not configured to operate with GDB and so errors are displayed. Single stepping at this point will simply walk through the assembly code rendition of the Android loader. It is much more useful to set a breakpoint at the beginning of our application and then continue to the breakpoint, as follows:
(gdb) b my_ping.c:main
Breakpoint 1 at 0xe120: file /home/cawilde/mydroid/external/myapps/myping/my_ping.c, line 1673.
(gdb) c
Continuing.
warning: .dynamic section for “/home/cawilde/mydroid/out/target/product/generic/symbols/system/lib/libc.so” is not at the expected address (wrong library or version mismatch?)
[New Thread 695]
warning: .dynamic section for “/home/cawilde/mydroid/out/target/product/generic/symbols/system/lib/libstdc++.so” is not at the expected address (wrong library or version mismatch?)
warning: .dynamic section for “/home/cawilde/mydroid/out/target/product/generic/symbols/system/lib/libm.so” is not at the expected address
warning: difference appears to be caused by prelink, adjusting expectations
warning: .dynamic section for “/home/cawilde/mydroid/out/target/product/generic/symbols/system/lib/liblog.so” is not at the expected address
warning: difference appears to be caused by prelink, adjusting expectations
warning: .dynamic section for “/home/cawilde/mydroid/out/target/product/generic/symbols/system/lib/libcutils.so” is not at the expected address (wrong library or version mismatch?)
[Switching to Thread 695]
Breakpoint 1, main (argc=2, argv=0xbeaead84)
at /home/cawilde/mydroid/external/myapps/myping/my_ping.c:1673
1673 {
Current language: auto; currently c
(gdb) l
1668 “ [ -T timestamp option ] [ -Q tos ] [hop1 ...] destinationn”);
1669 exit(2);
1670 }
1671
1672 int main(int argc, char *argv[])
1673 {
1674 struct hostent *hp;
1675 int ch, hold, packlen;
1676 int socket_errno;
1677 u_char *packet;
(gdb) l
1678 char *target, hnamebuf[MAXHOSTNAMELEN];
1679 char rspace[3 + 4 * NROUTES + 1]; /* record route space */
1680
1681 icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
1682 socket_errno = errno;
1683
1684 /* if I Ire setuid root, undo that */
1685 if (setuid(getuid())) return -1;
1686
1687 source.sin_family = AF_INET;
I can then single step code and examine variables in the usual GDB fashion:
(gdb) n
1681 icmp_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
(gdb) n
1682 socket_errno = errno;
(gdb) p socket_errno
$1 = 32768
(gdb) n
1685 if (setuid(getuid())) return -1;
(gdb) n
1687 source.sin_family = AF_INET;
(gdb) n
1689 preload = 1;
(gdb) p preload
$2 = 0
(gdb) n
1690 while ((ch = getopt(argc, argv, COMMON_OPTSTR “bRT:”)) != EOF) {
(gdb) p preload
$3 = 1
(gdb) n
1769 argc -= optind;
… etc ….
My preference is using an integrated GUI development environment, and the command line GDB, although quite useful, is a long way from that. Ideally the CDT add-on for Eclipse could be reconciled with the Android OSP build environment. This would take more development time to accomplish than I have available, so I will continue to use GDB for Android native debugging tasks.
In the next post, I will detail how to build native shared libraries for use with a Java wrapper or Java GUI application.