NDK開發歷程(一):android native code的調試方法

引用:http://www.cnblogs.com/ychellboy/archive/2013/02/22/2922683.htmlphp

  使用NDK在android上作開發是一件「痛並快樂着」的差事,之因此「快樂」是由於能夠將一些原有的C/C++庫直接移植到android上,而不須要用java再開發一套功能相同的庫。然而這同時也是一件「痛苦」的事件,由於android自己是裁減過的linux,好些system call不能使用,另外因爲沒有采用glibc(用的是Bionic libc,緣由見wiki),好些函數所在的頭文件位置也有變化,這都給移植工做帶來困難。更爲坑爹的是一些函數在頭文件裏能找到定義在具體庫裏確沒有實現(好比:pthread_mutex_timedlock)。html

    android native開發在編譯連接階段會遇到上述「慘痛」經歷,但更爲痛苦的是好不容易變成可執行文件,一運行就crash沒有任何信息。遇到這種狀況,在排除了代碼有低級錯誤的狀況後,最終只能想辦法作debug。(本文餘下篇幅在不特殊註明的狀況下都是指使用NDK在android上作native code的開發)。java

    在android上NDK開發的程序進行查錯主要有兩種方法:linux

(1)使用log進行查錯:在程序源代碼上加log,根據log信息來排查錯誤。這種方式應該是最爲經常使用的,由於其普適性很高。不過做爲在VxWorks上移植過網絡庫的苦逼,深知用log排錯的效率是多麼的低,特別是在排查底層庫時。而遇到多線程的程序,log排錯是多麼的無力。android

(2)使用ndk-gdb調試程序:用過gdb的都知道它多麼的強大,可是想要使用ndk-gdb須要作不少的配置,還會碰到不少坑,所以想真正使用起來也不是件容易的事(畢竟是開源項目,和VxWorks這種高富帥是無法比的)。c++

    本文主要介紹如何配置使用ndk-gdb進行debug,所使用android-ndk-r8d/samples/hello-jni做爲入口調用一個static library。 ——  Here we go!shell

 

1、開發環境編程

1. ubuntu 12.04 x86_64ubuntu

2. eclipse 3.7(只是爲了方便啓動android模擬器)windows

3. android NDK r8d

4. android SDK 2.2 ~ 4.2

5. ant (打包程序使用)

    在windows環境下能夠配置cygwin來實現ndk-gdb,本人在windows上使用相同方法也達到了效果,對cygwin的配置這裏再也不討論,有疑問能夠找google老師。

 

2、準備階段

    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/tools

export PATH

 

3、修改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信息。

 

4、萬事俱備

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] :

image

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

[4] 使用eclipse/ndk-gdb對java/native code聯合調試

[5] 把hello-jni的.c後綴改爲.cpp後出錯

 
 
分類:  Android開發
相關文章
相關標籤/搜索