一、需求背景
Android Studio 2.2+版本支持使用CMake構(gòu)建native庫,同時(shí)調(diào)試器也能對自構(gòu)建的native代碼進(jìn)行源碼的調(diào)試,我們可以像調(diào)試java代碼一樣方便地調(diào)試native代碼。
我們很多的項(xiàng)目都依賴的so庫,有些是我們單獨(dú)的庫工程中的,有些是三方提供的。如果想在主工程中調(diào)試這些庫的源碼,一種方法添加子工程或者CMake子模塊關(guān)聯(lián)源碼,作為自身構(gòu)建的一部分,當(dāng)然需要所有的子工程都編譯通過。
搭建多工程環(huán)境和構(gòu)建子工程本身就是一個耗時(shí)又麻煩的事情,我們希望和庫工程盡量不要耦合,而且調(diào)試時(shí)不需要再去構(gòu)建庫工程。
問題:我們本地有so庫對應(yīng)版本的源碼,也有帶符號so文件,如何進(jìn)行源碼的調(diào)試?
二、工程示例
這里我們示例一個簡單的庫工程mylib,它的c++代碼中封裝了一個求平均數(shù)的方法,然后java對外暴露了native的接口。
#include <jni.h>
extern "C"
JNIEXPORT jint JNICALL
Java_smarttime_tsia_com_mylib_MyLibNative_nativeCalAverage(JNIEnv *env, jclass type, jint a, jint b) {
int sum = a+b;
int avg = sum/2;
return avg;
}
MyLibNative.java
package smarttime.tsia.com.mylib;
public class MyLibNative {
static {
System.loadLibrary("test-lib");
}
public static native int nativeCalAverage(int a, int b);
}
然后我們將它打包成一個armeabi-v7a架構(gòu)的so庫(libtest-lib.so):
我們APP主工程中,依賴這個三方庫(可以通過fileTree或maven),有一個按鈕點(diǎn)擊觸發(fā)調(diào)用nativeCalAverage:
public class MainActivity extends AppCompatActivity {
...
void onClick(View v) {
int ret = MyLibNative.nativeCalAverage(3,5);
}
}
三、源碼調(diào)試
要在主工程中進(jìn)行源碼調(diào)試,首先要有源碼,這邊要注意要和主工程依賴的版本對應(yīng)。工程依賴的三方庫都是不帶符號的,我們可以解壓APK查看libtest-lib.so,stripped表示調(diào)試信息已經(jīng)去掉了
1. 獲取符號文件
符號文件中包含了行號信息,這樣調(diào)試器就知道符號地址對應(yīng)的行號,也包含源碼文件的信息,這樣在IDE中調(diào)試器就能調(diào)到對應(yīng)的文件中的某一行了。

2. 調(diào)試過程和原理
1)啟動調(diào)試器
可通過Debug 'app'或者Attach Debugger to Process的方式,后者無需重啟應(yīng)用,注意選擇Debug Type為native,因?yàn)槲覀円{(diào)試的是native代碼。
2)設(shè)置斷點(diǎn)
調(diào)試器連接上進(jìn)程之后,點(diǎn)擊pause program,然后我們在jni方法Java_smarttime_tsia_com_mylib_MyLibNative_nativeCalAverage設(shè)置一個斷點(diǎn),source info可以查看當(dāng)前堆棧的源碼和行號信息,但這時(shí)候我們還沒有符號文件,所以只能看到一個它的地址。
3)添加符號文件
add-dsym可以添加指定的符號文件,如果這時(shí)候libtest-lib.so還沒有加載進(jìn)來就會添加失敗,匹配不到對應(yīng)的模塊。

4)觸發(fā)斷點(diǎn)
斷點(diǎn)觸發(fā)以后,source info查看到當(dāng)前的行號已經(jīng)有了,源碼信息也指向我們工程中打開的源碼文件,所以IDE就跳到對應(yīng)的斷點(diǎn)上了,然后我們就可以進(jìn)行單步調(diào)試、條件斷點(diǎn)、watch point等調(diào)試了。
實(shí)際調(diào)試中只要調(diào)試器連接到進(jìn)程,然后添加調(diào)試符號就行了,然后Android Studio中在對應(yīng)的源碼文件中直接打斷點(diǎn)調(diào)試即可,上述過程只是方便分析原理。
3. 源碼映射
上述的符號文件和源碼是在庫工程里,符號文件是源碼構(gòu)建生成的,所以我們添加了符號文件就能調(diào)試源碼了。實(shí)際在很多項(xiàng)目中庫構(gòu)建和發(fā)布都是在一臺專門用來打包的服務(wù)器(云端)上進(jìn)行的,本地只有對應(yīng)版本的源碼,為了保證一致性,主工程依賴的是打包機(jī)器發(fā)布的庫,我們也用打包機(jī)器上生成的符號文件來調(diào)試。
打包機(jī)器和本地生成的符號文件唯一的不同就是構(gòu)建環(huán)境,也就是源碼文件的路徑信息。我們來模擬一下,假設(shè)上述在mylib工程的目錄就是打包發(fā)布的專用目錄,而我們要調(diào)試的native代碼放在了另一個目錄里。
符號文件添加后,斷點(diǎn)到j(luò)ni方法,雖然有行號信息,但I(xiàn)DE中并沒有調(diào)到對應(yīng)的文件,因?yàn)檫@時(shí)候調(diào)試器只知道符號文件的源碼信息,它沒有指向本地要調(diào)試的源碼。打包時(shí)環(huán)境:/Users/derek/Documents/AndroidProject/jniTest2/mylib/src/main/jni/test.cpp
調(diào)試源碼路徑:/Users/derek/Downloads/src/test.cpp


4. Android Studio Debugger配置
以上我們都是通過執(zhí)行l(wèi)ldb命令來添加符號、源碼映射的,如果覺得麻煩Android Studio也提供了Debugger配置:
Symbol Directories為符號目錄,調(diào)試器會遞歸的尋找這個目錄下的文件,直到匹配到需要的符號文件為止;LLDB Post Attach Commands為調(diào)試器連接到進(jìn)程后執(zhí)行的命令,我們可以在這里添加setting set target.source-map源碼映射的命令。
如果通過Debugger配置來調(diào)試,必須用Debug 'app'的方式,不能使用Attach的方式,因?yàn)橹挥星罢卟艜?zhí)行Debugger里的命令。
參考:
http://lldb.llvm.org/tutorial.html
https://developer.android.com/studio/run/rundebugconfig?hl=zh-cn#definingbefore