NDK开发历程(一):android native code的调试方法
使用NDK在android上做开发是一件“痛并快乐着”的差事,之所以“快乐”是因为可以将一些原有的C/C++库直接移植到android上,而不需要用java再开发一套功能相同的库。然而这同时也是一件“痛苦”的事件,因为android本身是裁减过的linux,好些system call不能使用,另外由于没有采用glibc(用的是Bionic libc,原因见wiki),好些函数所在的头文件位置也有变化,这都给移植工作带来困难。更为坑爹的是一些函数在头文件里能找到定义在具体库里确没有实现(比如:pthread_mutex_timedlock)。
android native开发在编译链接阶段会遇到上述“惨痛”经历,但更为痛苦的是好不容易变成可执行文件,一运行就crash没有任何信息。遇到这种情况,在排除了代码有低级错误的情况后,最终只能想办法做debug。(本文余下篇幅在不特殊注明的情况下都是指使用NDK在android上做native code的开发)。
在android上NDK开发的程序进行查错主要有两种方法:
(1)使用log进行查错:在程序源代码上加log,根据log信息来排查错误。这种方式应该是最为常用的,因为其普适性很高。不过作为在VxWorks上移植过网络库的苦逼,深知用log排错的效率是多么的低,特别是在排查底层库时。而遇到多线程的程序,log排错是多么的无力。
(2)使用ndk-gdb调试程序:用过gdb的都知道它多么的强大,但是想要使用ndk-gdb需要做很多的配置,还会碰到很多坑,因此想真正使用起来也不是件容易的事(毕竟是开源项目,和VxWorks这种高富帅是没法比的)。
本文主要介绍如何配置使用ndk-gdb进行debug,所使用android-ndk-r8d/samples/hello-jni作为入口调用一个static library。 —— Here we go!
一、开发环境
1. ubuntu 12.04 x86_64
2. eclipse 3.7(只是为了方便启动android模拟器)
3. android NDK r8d
4. android SDK 2.2 ~ 4.2
5. ant (打包程序使用)
在windows环境下可以配置cygwin来实现ndk-gdb,本人在windows上使用相同方法也达到了效果,对cygwin的配置这里不再讨论,有疑问可以找google老师。
二、准备阶段
1. 下载linux平台的NDK,并解压到相应目录。这里需要注意的是:虽然google网站上写着NDK for Linux 32/64-bit(x86),但是ndk中的一些工具(比如NDK自带的awk,make,sed)在64bit的ubuntu上并不能直接运行,因为这些工具是32bit的程序,需要32bit的运行时库。解决方法是:sudo apt-get install libc6-i386, sudo apt-get install lib32asound2 lib32z1 lib32stdc++6 lib32bz2-1.0 安装这些常用的32位库。如果是CentOS则需要:yum install libgcc.i686 yum install glibc-static.i686 yum install glibc-devel.i686
2. 下载SDK,在下载页面的”DOWNLOAD FOR OTHER PLATFORMS“ –>“ADT Bundle”找到对应的版本下载,并解压到相应的目录。这时SDK下的platforms会有最新版本的android,下载历史版本的android就要使用tools下的工具:./android list sdk 根据列举出来的编号执行如下: ./android update sdk –t 1 –u 则更新编号是1的包。使用android update把所需要的历史版本都下载下来。
3. 根据实际情况在~/.profile(或~/.bash_profile)中设置如下环境变量,设置完毕后执行source ~/.profile使之生效:
# ---- NDK ----
NDK_ROOT=~/mysoftware/NDK/android-ndk-r8d
PATH=$PATH:$NDK_ROOT
export NDK_ROOT# ---- android-SDK ----
ANDROID_SDK_ROOT=~/mysoftware/SDK/adt-bundle-linux-x86_64/sdk
PATH=$PATH:$ANDROID_SDK_ROOT
export ANDROID_SDK_ROOT# ---- adb ----
ADB_PATH=~/mysoftware/SDK/adt-bundle-linux-x86_64/sdk/platform-tools
PATH=$PATH:$ADB_PATH# ---- tools/android ----
PATH=$PATH:~/mysoftware/SDK/adt-bundle-linux-x86_64/sdk/toolsexport PATH
三、修改hello-jni
由于项目使用c++编程,做这个实验的时候就将jni/hello-jni.c 改为hello-jni.cpp,代码如下:
1: #include <string.h>
2: #include <jni.h>
3: #include <unistd.h>
4: #include "shared/thread.h"
5:
6: /* This is a trivial JNI example where we use a native method
7: * to return a new VM String. See the corresponding Java source
8: * file located at:
9: *
10: * apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
11: */
12:
13: using namespace shared;
14:
15: extern "C" {
16:
17: void* StartThread(void* obj)
18: {
19: return NULL;
20: }
21:
22: jstring
23: Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
24: jobject thiz )
25: {
26: volatile int bGo = 0;
27: while(!bGo) {
28: sleep(1);
29: }
30:
31: Thread mythread(&StartThread, NULL);
32: mythread.Start();
33:
34: return env->NewStringUTF("Hello from JNI !");
35: //return (*env)->NewStringUTF(env, "Hello from JNI !");
36: }
37:
38: }
注意需要用extern “C”{ } 把Java_com_example_hellojni_HelloJni_stringFromJNI函数包起来,while (!bGo)是为了方便调试,因为ndk-gdb会先把程序run起来后再attach上去,这里需要一个while让程序等一会。上述代码中的Thread类是在libshared.a的静态库中,因此需要修改hello-jni目录下的jni/Android.mk文件。如下:
1: LOCAL_PATH := $(call my-dir)
2:
3: include $(CLEAR_VARS)
4: LOCAL_MODULE := shared
5: LOCAL_SRC_FILES := ../shared/obj/local/armeabi/libshared.a
6: LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/shared
7: include $(PREBUILT_STATIC_LIBRARY)
8:
9: include $(CLEAR_VARS)
10: LOCAL_MODULE := hello-jni
11: LOCAL_SRC_FILES := hello-jni.cpp
12: LOCAL_STATIC_LIBRARIES := shared
13: LOCAL_C_INCLUDES := $(LOCAL_PATH)/../
14:
15: include $(BUILD_SHARED_LIBRARY)
红色部分为添加或修改项,编译前需要在环境变量C_INCLUDE_PATH中加入jni.h的路径,比如:
1: C_INCLUDE_PATH=$C_INCLUDE_PATH:~/mysoftware/NDK/android-ndk-r8d/platforms/android-8/arch-arm/usr/include
2:
3: export C_INCLUDE_PATH
PS:libshared.a在build时需要加NDK_DEBUG=1的参数,即:ndk-build NDK_DEBUG=1,这么编译才能带上debug信息。
四、万事俱备
1. shell进入ndk/samples/目录,运行android update project --path hello-jni,生成build.xml用于apk打包。(也可以在hello-jni目录里运行:android update project -t 1 -p . --subprojects)
2. 进入ndk/samples/hello-jni,修改AndroidManifest.xml文件
1: <application android:label="@string/app_name"
2: android:debuggable="true">
3. 运行ndk-build
4. 运行ant debug
5. 启动android的模拟器(可以从eclipse启动)
6. 运行adb install –r bin/HelloJni-debug.apk
7. 运行ndk-gdb –start 开始debug,后续和使用gdb一样
8. 需要图形化界面进行debug,可以参考[2]
几点重要说明:
1. ndk-gdb用的是client/server形式对目标机器进行debug, gdb 调试器 与 gdbserver 的关系,就是 gdb 与 stub的关系,如下图所示[3] :
2. ndk-gdb最坑爹的是:gdb和gdbserver的版本必须是匹配的才能debug:
每一个模拟器在system/bin下都有gdbserver,这些gdbserver是和模拟器本身的android版本有关的,而下载的NDK的ndk-gdb一般都是最新的gdb,因此gdb和gdbserver的版本常常匹配不了。这时需要把对应版本的gdbserver push到emulator上,然后指定./gdbserver,必须指定“./”因为在linux下默认优先查找system目录。
References:
[1] 使用eclipse/ndk-gdb对java/native code联合调试
[2] Eclipse+CDT+GDB调试android NDK程序
[3] ndk-gdb对java/native code联合调试