前言
LLDB是我们平常在开发过程中的默认调试器
如上图所示:
lldb还有一个lldb-driver(驱动)- 外层的
API我们可以通过C++或者Python来使用 - 支持的文件格式不仅有
MachO还有ELF - 符号表支持
DWARF格式 - 汇编方面由
LLVM进行支撑 - 处理是由
GDB-Remote来完成
如图所示:
- 其实我们使用
lldb来调试的时候,lldb放在本地端(一般就是电脑端)而lldb-server是放在远程端(我们调试使用的手机)或者还是在本地端(使用模拟器调试) - 用户使用
命令行、Xcode、Python来使用 lldb和lldb-server则是使用TCP协议来通信,其中还有就是遵循GDB的一个基于字节流的协议
接下来可以来验证lldb和lldb-server通过一个字节流的协议来通信这个说法:
- 首先进入
lldb环境 file 可执行文件log enable gdb-remote packets- 之后可以看到不断的在发送和接收数据
lldb的流程调试
在编译好的llvm项目中找到本次需要使用的target lldb,在 Driver 文件中找到main函数,这里就是lldb的入口,首先可以先看一下这里传入的参数
参数分析:
argc代表传入了一个参数parray 1 argv表示从数组起argv始地址开始展示1个元素,可以看到这里就是只有一个参数就是lldb
再往下看可以看到这里会有一处判断输入的命令中是否会有help关键字,如果有的话会进行打印
那么这里如何在不改变代码的同时想看到打印呢?
bool hasArg(OptSpecifiers ...Ids) const {
return getLastArg(Ids...) != nullptr;
}
思路:
- 首先看到是调用了函数
hasArg - 可以看到这里会有一次比较,那么如果在函数返回之前改变了结果就可以实现不更改代码而改变返回值的效果
使用命令image lookup -n "hasArg"在可执行文件的镜像中寻找hasArg的信息
di -s 0x0000000100004774在刚找到的地址进行汇编代码展示,目的是找到在函数返回前修改返回值
在汇编代码中可以看出在函数返回之前最终的结果是存放于寄存器w0中
br set -a 0x1000047a8在这个地址的位置加入断点,可以看到断点是断在了函数hasArg中
那么在这里进行给寄存器w0重新赋值即可
register write w0 1,之后接着运行控制台打印出了与我们直接在终端使用命令lldb --help的效果是一样的
命令总结:
-
image lookup -n "hasArg" -
di -s 0x0000000100004774 -
br set -a 0x1000047a8 -
register write w0 1
lldb 在实际开发的应用
现在设置一个场景:在一个封装的定位器中有一个属性标识了定位是否成功,那么在每次调试的时候为了方便需要这个标识是成功的,但是不好给它写死,因为这样会修改代码调试好了后又要删掉这里,会很不方便。那么这时就可以使用lldb来调试
利用行号下断点
可以在52行下断点然后直接更改isLocationSucess的值为true
进入lldb环境后使用命令br set -l 52 -f ViewController.m -C "e -- _manager.isLocationSucess = YES" -G true
命令解析:
-l是指定52行-f是指定文件ViewController.m-C是指触发此断点后执行后面的命令,即修改isLocationSucess为TRUE-G是指此断点在执行后面的命令后不会有触感
执行命令后显示修改成功
但是这里用行号去打断点会显得十分不灵活,因为谁都无法保证这个文件不会去改动,一旦改动后用行号打断点的命令就会失效
利用源码下断点
lldb环境下使用命令br set -p "if \(_manager.isLocationSucess\) " -f ViewController.m -C "e -- _manager.isLocationSucess = YES" -G true
命令解析:
-p对符合正则表达式的源码处下断点-f指定文件-C是指触发此断点后执行后面的命令-G是指此断点在执行后面的命令后不会有触感
但是这个方法也不能保证此处代码不被更改
对init方法下断点
对init方法下断点的同时去修改实例对象manager的isLocationSucess的属性
image lookup -vrn "\[LGLocationManager init\]"找到init方法的范围
di -s 0x0000000104191770 -c 36从init方法初始地址开始展示36行的汇编代码
在函数返回之前设置断点,x0寄存器此时存储的就是manager,这时对isLocationSucess进行修改
br set -a 0x1041917fc在返回处设置断点
br command add在前一个断点出添加命令
Enter your debugger command(s). Type 'DONE' to end.
> e -- [(LGLocationManager *)$x0 setIsLocationSucess:YES]
> continue
> DONE
对sp中的地址下断点
br set -S "-[KKLocationManager init]" -K false对init符号设置断点
di -f显示断点处的汇编指令,也以便能拿到sp、x0寄存器
br command add对上一个断点添加指令
> br s -a `*(unsigned long *)$sp`
> e -- [(KKLocationManager *)$x0 setIsLocationSucess:YES]**
> continue
> DONE
添加指令的作用:
- 对
sp寄存器中的地址下断点 - 在栈顶指针中有值的时候此时
x0中的值已经设置完毕了 - 修改
x0中内容
可以看到已经修改成功
更改返回的字符串
thread return
thread return是可以更改当前栈返回的默认值
比如下图中,点击后返回的应当是Dog
e -- NSString *$name = @"Cat"在lldb中创建一个变量
br set -r thread_return -C "thread return `$name`" -G true
使用thread return把返回值更改为之前创建的新变量
但是这个会有新的问题,会显示BAD_ACCESS
修改内存
先来看看str到底是什么
str内存偏移16个字节存储的东西就是@"Cat"字符串,下面可以来验证
image lookup -a 0x0000000100cdbdd7可以根据地址来查找看看这个字符串到底是在哪里的
可以清楚的看到是在MachO的TEXT段的cstring中存放,那么同理@"Dog"这个字符串应该也是存放在这个位置,既然是这样就可以更改内存来达到目的
re read x0这个函数的返回值是存放在x0寄存器中的
me read -s 8 -f x -c 4 0x0000000100cdc088的意思:
-s 88个字节为一组-f x16进制的格式-c 4总共读4组
e -f x -- (unsigned long *)($x0 + 16)偏移16个字节的指针中存放的就是@"Dog"
me write -s 8 `(unsigned long *)($x0 + 16)` 0x0000000100cdbdd7
将原来存放@"Dog"的指针更改其内容为@"Cat"
更改成功
lldb中的记录和重播的功能
从文档中可以看到这里是告诉调试器去捕获一个复制器
lldb --capture
file next
file test
b main
br list
r
di -f -m
reproducer status
reproducer generate
lldb --replay /var/folders/nb/s5qd3zpx4ksg7ck7xj7mf24c0000gn/T/reproducer-e1bafe
--replay就是告诉调试器通过后面路径的文件来重播复制器,可以看到上面执行的命令会再次执行一遍