首先聲名本文中的調試教程需要Android Root環境, 非越獄環境請使用Android Studio
筆者是從iOS開發轉到Android的, 所以之前對lldb有一定的了解, 在iOS中我們可以使用debug-server+lldb調試iOS應用,前段時間正好做了一個Android端的native程序, 同樣想單步調試一下c++代碼,一開始想到使用gdbserver+gdb來調試但弄來弄去, 好像版本不匹配怎么也調不起來. 於是轉念一想能不能用lldb呢, 畢竟Android Studio調試JNI使用lldb已經非常流行, 而且AS在逐步淘汰gcc的東西,似乎llvm前途更加光明
前提
要調試的目標程序編譯時加了 -g -O0 選項, 只有這樣程序才會包含調試信息, 否則你會發現,很多代碼中的變量,無法訪問, 且沒辦法看到源碼.
准備
1. 獲取lldb-server
1.1 從NDK中獲得
ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/9.0.8/lib/linux/aarch64/lldb-server
NDK中有4個lldb-server, 由於我的設備是64位的, 所以選擇aarch64目錄下的
1.2 從設備中提取
如果你的手機,曾經使用AS調試過JNI代碼的話,看下手機的 /data/local/tmp 目錄
adb shell ls -l /data/local/tmp
adb pull /data/local/tmp/lldb-server lldb-server
沒有也不用擔心, 隨便用AS new 一個新工程, 選擇Native C++. 然后在cpp文件中打個斷點, 調試一下, 手機上自然就有了.
2. 啟動lldb-server
將lldb-server放入設備並啟動, 或者直接使用 /data/local/tmp/lldb-server就行
$ adb push lldb-server /data/local/tmp/
$ adb shell
cd /data/local/tmp
chmod 755 lldb-server
./lldb-server p --server --listen unix-abstract:///data/local/tmp/debug.sock
3.lldb連接lldb-server
$ lldb
platform list # 看下lldb可以連接的平台
platform select remote-android
platform status # 查看平台狀態
platform connect unix-abstract-connect:///data/local/tmp/debug.sock
注意看下 WorkingDir: /data/local/tmp 說明當前遠端工作目錄在這兒
調試
方式一
file [target_binary] # 指定將要調試的二進制文件,注意是相對於WorkingDir的路徑
br set -f app_core.cpp -l 128 # 意思就是在app_core.cpp的128行處打個斷點
run
此時Native程序應該已經啟動起來, 如果斷點在啟動邏輯處, 應該也觸發到斷點了, 看看~ 多強大, 連代碼里的注釋都有, 簡直不要太方便 😃
Process 6855 stopped
* thread #1, name = 'xxxx', stop reason = breakpoint 1.1
frame #0: 0x0041343c xxxx`AppCore::run(this=0xf731f000) at app_core.cpp:128:17
125
126 // Initialize config
127 core_server.init_config();
-> 128 core.set_reuse(true);
129
130 // 配置xxx
以上這種方式方便調試那種一啟動就崩潰的bug, 可以在應用啟動前打好斷點, 啟動時即可觸發斷點(launch breakpoint).
方式二
假如程序已經運行起來了, 我們可以用attach的方式, 將調試器附到可調試進程中, 並在目標文件打斷點, 來調試具體邏輯
file [target_binary] # 指定將要調試的二進制文件,注意是相對於WorkingDir的路徑
platform process list # 查看一直遠端的進程, 找到目標進程pid, 或者名稱
attach 9053
此時,若附加成功,程序會斷住 SIGSTOP, 像這樣
Process 9053 stopped
* thread #1, name = 'xxx', stop reason = signal SIGSTOP
frame #0: 0xf03a38e8 libc.so`__epoll_pwait + 20
libc.so`__epoll_pwait:
-> 0xf03a38e8 <+20>: pop {r4, r5, r6, r7}
0xf03a38ec <+24>: cmn r0, #4096
0xf03a38f0 <+28>: bxls lr
0xf03a38f4 <+32>: rsb r0, r0, #0
thread #2, name = 'xxx', stop reason = signal SIGSTOP
frame #0: 0xf03a3834 libc.so`__accept4 + 8
libc.so`__accept4:
-> 0xf03a3834 <+8>: svc #0x0
0xf03a3838 <+12>: mov r7, r12
0xf03a383c <+16>: cmn r0, #4096
0xf03a3840 <+20>: bxls lr
OK, 現在就是設置斷點的時機, 像方式一那樣, 打斷點, 然后
br set -f app_core.cpp -l 128
continue # 繼續運行程序
當觸發到斷點時, 又可以愉快的調戲你的代碼了.
LLDB 命令
lldb使用起來非常的方便, 有什么不會的, 直接在lldb中打help, 所有命令及解釋明明白白, 當然你也可以上網扒扒LLDB的命令, 文章無數, 筆者這里列一些常用的命令供參考
lldb命令支持自動補全和簡寫, 所以上文中的 br = breakpoint. 類似的還有 c = continue. 只要不會和其它命令產生混淆, 使用簡寫也是完全沒問題的, 若有歧義, 聰明的lldb也會友好的給出提示.
b = breakpoint 設置斷點
c = continue 繼續運行
n = next 下一行
s = step 單步進入
f = finish 跳出
p [var] 打印變量值
var 顯示所有局部變量
bt 打印調用棧
up 在調用棧中向上移一幀 older
down 在調用棧中下移一幀 newer
register 查看寄存器
memory 查看內存