使用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聯合調試