LLDB调试Android Native程序

首先声名本文中的调试教程需要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 查看内存

参考

LLDB

posted @ 2020-12-18 15:57  Coding&Life  阅读(9055)  评论(3编辑  收藏  举报