【工具使用】Android调试利器之LLDB

本文介绍在Android开发中如何使用LLDB进行NDK调试,包括设置断点、程序流程控制、内存修改等基本操作,以及逆向工程和无源码调试的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文来自 网易云社区 。

在Android开发当中,或多或少都会接触到NDK的开发。Android Studio作为官方IDE,对NDK的支持也日趋完善,到目前为止,集成了基于CMake的构建工具,以及基于LLDB的NDK调试工具,早在Android之前,LLDB已经成为iOS开发中不可或缺的工具,本文简单介绍一下LLDB的一些常规使用,抛砖引玉,先来看一下我们的示例程序。

 

在页面启动的时候,会从native-lib中读取字符串,然后展示在UI上,native代码如下

 

进入调试状态

通过Android Studio的菜单,选择Attach process,或者直接以调试方式启动程序。 Debugger选择Auto/Native/Hybrid都可以,当然也可以自己push LLDB server到手机设备,然后手动远程连接上去,可以参见远程调试

 

成功连接调试器之后,Android中集成了LLDB的控制台,在控制台中我们可以通过一系列命令和调试程序进行交互。

LLDB语法简介

<noun> <verb> [-options [option-value]] [argument [argument…]]

LLDB的命令都符合上述格式,argument option 以及option-value用空格隔开,LLDB支持前缀匹配,大多数情况下并不需要输入完整命令。如果参数中有空格,则需要用“”进行保护。option可以放在命令中的任意位置,不过如果argument中有以-开始的值,可以通--显示的标识option的结束。 另外还有一些转义规则请参考官方文档

设置断点

进入调试状态之后,紧接着就是下断点了,在LLDB中通过breakpoint set命令进行断点的设置。

#按文件和行号设置断点
(lldb) breakpoint set -f native-lib.cpp -l 9
Breakpoint 2: where = libnative-lib.so`getString(int) + 6 at native-lib.cpp:9, address = 0x73a3cf5a
#按函数名设置断点,不区分C函数和C++Method
(lldb) breakpoint set -n getString
Breakpoint 3: 20 locations.
#按C++方法名设置断点
(lldb) breakpoint set -M getString
Breakpoint 4: 18 locations.
#通过-s参数制定module
(lldb) breakpoint set -s libnative-lib.so -n getString
Breakpoint 5: where = libnative-lib.so`getString(int) + 6 at native-lib.cpp:9, address = 0x73a3cf5a

条件断点

(lldb) breakpoint modify 2 -c 'index == 0'
(lldb) breakpoint list
Current breakpoints:
2: file = 'native-lib.cpp', line = 9, exact_match = 0, locations = 1, resolved = 1, hit count = 0
Condition: index == 0

  2.1: where = libnative-lib.so`getString(int) + 6 at native-lib.cpp:9, address = 0x73a3cf5a, resolved, hit count = 0

通过-c参数告知调试器,当给定表达式的计算结果为true时,触发断点。

地址断点

在逆向其他应用,或则无源码调试的时候,我们往往需要按照地址设置断点

(lldb) breakpoint set -a 0x73a3cf5a

 

通过-a参数指定地址,在没有其他参数的情况下,这个地址是进程空间的地址,在实际无源码调试过程中,我们可以通过静态分析工具(arm-eabi-objdump/ida)获取到需要断点指令的file offset,然后通过下面的指令设置断点。

(lldb) breakpoint set -s libnative-lib.so -a 0xf04

 

其他断点相关命令

#查看所有断点
(lldb) breakpoint list
Current breakpoints:
2: file = 'native-lib.cpp', line = 9, exact_match = 0, locations = 1, resolved = 1, hit count = 0
  2.1: where = libnative-lib.so`getString(int) + 6 at native-lib.cpp:9, address = 0x73a3cf5a, resolved, hit count = 0
#禁用断点
(lldb) breakpoint disable 2
1 breakpoints disabled.
(lldb) breakpoint list
Current breakpoints:
2: file = 'native-lib.cpp', line = 9, exact_match = 0, locations = 1 Options: disabled
  2.1: where = libnative-lib.so`getString(int) + 6 at native-lib.cpp:9, address = 0x73a3cf5a, unresolved, hit count = 0
#启用断点
(lldb) breakpoint enable 2
1 breakpoints enabled.
#删除断点
(lldb) breakpoint delete 2
1 breakpoints deleted; 0 breakpoint locations disabled.

程序流程控制

当调试器命中断点之后,我们可以开始程序的流程控制。

#源码级别的流程控制
(lldb) thread step-in
(lldb) thread step-over
(lldb) thread step-out
(lldb) thread continue
#指令级别的流程控制
(lldb) thread step-inst
(lldb) thread step-inst-over
#所有线程恢复执行
continue
#查看调用栈
(lldb) thread backtrace
* thread #3: tid = 8148, 0x73a3cf5a libnative-lib.so`getString(index=0) + 6 at native-lib.cpp:9, name = 'om.netease.demo', stop reason = breakpoint 2.1
  * frame #0: 0x73a3cf5a libnative-lib.so`getString(index=0) + 6 at native-lib.cpp:9
    frame #1: 0x73a3cf9c libnative-lib.so`::Java_com_netease_demo_MainActivity_stringFromLibNative(env=0x41894638, (null)=0x3770001d) + 24 at native-lib.cpp:20
    frame #2: 0x409043d0 libdvm.so`dvmPlatformInvoke + 116
    frame #3: 0x40934d92 libdvm.so`dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*) + 402
    frame #4: 0x4090d8a8 libdvm.so`dvmJitToInterpNoChain + 696

 

内存修改

在调试的过程中,我们往往需要查看内存变量的值,可以通过frame或则p/po/x指令来实现。frame用来查看当前栈帧的变量值,p和po用来打印表达式的结果,x用来查看内存中某个地址开始的值,对于不可打印的值,可以通过x查看。frame select指令可以用来切换当前所处的栈帧,but Android Studio使用的lldb server有问题,目前这个指令无法生效,详见issues

(lldb) frame info
frame #0: 0x73a3cf5a libnative-lib.so`getString(index=0) + 6 at native-lib.cpp:9
(lldb) frame variable
(int) index = 0
#打印变量的值
(lldb) p index
(int) $2 = 0
(lldb) po index
0
#修改变量的值
(lldb) expression index = 1
(int) $0 = 1

 

expression指令用于在当前栈帧中计算表达式,我们可以用其修改变量的值,在上面的列子中,我们把index的值换成1,从而返回data[1],那考虑下如果我们期望返回一个全新的字符串,比如”New String from Lib Native”?

(lldb) thread step-out
(lldb) expression str = "New String from Lib Native"

 

很遗憾,这样的命令并不能达到预期的效果。要找到原因,我们可以使用disassemble命令进行反编译。

(lldb) disassemble
libnative-lib.so`::Java_com_netease_demo_MainActivity_stringFromLibNative(JNIEnv *, jobject):
    0x73a3df84 <+0>:  push   {r7, lr}
    0x73a3df86 <+2>:  mov    r7, sp
    0x73a3df88 <+4>:  sub    sp, #0x18
    0x73a3df8a <+6>:  mov    r2, r1
    0x73a3df8c <+8>:  mov    r3, r0
    0x73a3df8e <+10>: str    r0, [sp, #0x14]
    0x73a3df90 <+12>: str    r1, [sp, #0x10]
    0x73a3df92 <+14>: movs   r0, #0x0
    0x73a3df94 <+16>: str    r2, [sp, #0x8]
    0x73a3df96 <+18>: str    r3, [sp, #0x4]
    0x73a3df98 <+20>: blx    0x73a3ddd4                ; symbol stub for: getString(int)
->  0x73a3df9c <+24>: str    r0, [sp, #0xc]
    0x73a3df9e <+26>: ldr    r1, [sp, #0x14]
    0x73a3dfa0 <+28>: str    r0, [sp]
    0x73a3dfa2 <+30>: mov    r0, r1
    0x73a3dfa4 <+32>: ldr    r1, [sp]
    0x73a3dfa6 <+34>: blx    0x73a3dde0                ; symbol stub for: _JNIEnv::NewStringUTF(char const*)
    0x73a3dfaa <+38>: add    sp, #0x18
    0x73a3dfac <+40>: pop    {r7, pc}

 

由此可见getString返回之后的结果从r0存储到栈上,然后加载到r1中作为NewStringUTF的参数。

#读寄存器
(lldb) register read r0
      r0 = 0x73a3f628  
(lldb) po (const char*)0x73a3f628
"Hello from Lib Native”

当用expression指令修改str的值,改的只是内存里面的值,r0中的地址并没有改变,所以要实现我们预期的效果,只需要修改r0中的值即可。

(lldb) expression str = "New String from Lib Native"
(const char *) $13 = 0x7437b020 "New String from Lib Native"
(lldb) register write r0 0x7437b020

 

线程

#查看线程
(lldb) thread list
Process 8148 stopped
  thread #1: tid = 6997, 0x40052534 libc.so`__ioctl + 8, name = 'Binder_8'
* thread #3: tid = 8148, 0x73a3cf5a libnative-lib.so`getString(index=0) + 6 at native-lib.cpp:9, name = 'om.netease.demo', stop reason = breakpoint 2.1
  thread #4: tid = 8152, 0x40053844 libc.so`__futex_syscall3 + 8, name = ‘GC'
#切换线程
(lldb) thread select 3

 

结语

LLDB的命令非常强大,熟练掌握常用命令对JNI开发调试过程大有裨益,本文只简单介绍了一些基本用法,后续在使用过程中对命令有问题,可以通过help或者官方文档获取帮助。

原文:【工具使用】Android调试利器之LLDB

本文来自网易云社区,经作者唐杰授权发布。

<think>好的,我现在需要帮助用户了解如何在Android环境中配置和使用LLDB调试器。首先,我应该回顾用户提供的引用内容,特别是引用1、2和3,这些内容提到了LLDBAndroid Studio中的集成,以及一些配置方法。用户可能希望从基础步骤开始,比如环境准备、配置过程、调试方法等。 首先,我需要确认用户的环境需求,比如Android Studio的版本、NDK的安装情况,以及是否已经配置了gradle-experimental插件。根据引用1,LLDB需要从SDK Tools中下载最新版本,并配合Android Studio和gradle-experimental使用。这可能涉及到SDK Manager中的安装步骤。 接下来,配置项目以支持LLDB调试。根据引用2,用户需要在运行/调试配置中选择使用LLDB,并设置相关的启动命令和符号目录。这里可能需要修改build.gradle文件,启用调试符号,并确保NDK版本正确。此外,引用3提到用户可能更推荐使用VSCode或Clion,但用户的问题明确是关于Android环境的,因此需要集中在Android Studio的配置上。 调试步骤方面,需要指导用户如何设置断点,启动调试会话,以及使用LLDB的命令行界面。可能还需要解释一些常用的LLDB命令,比如打印变量、查看调用栈等。同时,要提醒用户关于符号目录和连接后命令的设置,如引用2所述。 另外,用户可能遇到常见问题,比如无法断点命中或符号加载失败。需要根据引用中的信息,检查NDK版本、符号路径是否正确,以及调试器权限是否足够。可能还需要建议用户参考官方文档或社区资源,如引用1和2提到的内容。 最后,生成相关问题,帮助用户进一步学习,比如优化调试效率的方法,与GDB的区别,或结合其他工具如VSCode使用LLDB。根据引用3,可以提及替代工具,但主要回答应围绕Android Studio的环境。 总结步骤:环境准备→配置项目→调试步骤→常见问题→相关资源。确保每个步骤清晰,引用正确,并符合用户提供的格式要求,如行内公式使用$...$,独立公式用$$...$$,但这里可能不需要数学公式,重点在配置和命令。同时,保持回答结构清晰,使用中文,并在引用处添加标识符如[^1]。</think>### Android环境中使用LLDB调试器配置与使用教程 #### 一、环境准备 1. **安装Android Studio及NDK** - 确保已安装Android Studio(推荐最新稳定版) - 通过SDK Manager安装NDK和LLDB工具: **路径**:`Tools > SDK Manager > SDK Tools > 勾选NDK和LLDB` ![SDK Manager配置示例](https://example.com/sdk-manager-lldb.png)(注:此处为示意图) 2. **项目配置** - 在`build.gradle`中启用调试符号与LLDB支持: ```gradle android { defaultConfig { externalNativeBuild { cmake { arguments "-DCMAKE_BUILD_TYPE=Debug" } } } } ``` #### 二、配置调试流程 1. **创建调试配置** - 点击`Run > Edit Configurations`,选择`Android Native`类型 - 在`Debugger`选项卡中选择`LLDB`,并配置符号目录(如`.so`文件路径)[^2] - 设置启动命令(例如设置断点): ``` breakpoint set --name main ``` 2. **附加到进程调试** - 启动应用后,点击`Run > Attach Debugger to Android Process` - 选择目标进程并勾选`Show all processes`以查看Native进程 #### 三、调试操作指南 1. **基础命令** - 查看变量:`frame variable [变量名]` - 断点管理: ```bash breakpoint set --file test.cpp --line 20 # 设置断点 breakpoint delete 1 # 删除断点 ``` - 单步执行:`next`(跳过函数)或`step`(进入函数) 2. **查看调用栈** - 输入`bt`(backtrace)可查看当前线程的调用栈 #### 四、常见问题与解决 1. **断点未命中** - 检查NDK版本是否兼容(推荐≥r21) - 确认`.so`文件包含调试符号(编译时添加`-g`标志) 2. **符号加载失败** - 在LLDB配置中手动添加符号路径: ```bash settings set target.symbol-path /path/to/symbols ``` #### 五、相关工具与扩展 - **VSCode + LLDB插件**:提供更友好的调试界面(如内存查看、可视化变量跟踪)[^3] - **Clion**:适合复杂C/C++项目的跨平台调试(需配合Android项目配置) ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值