LLDB全称是Low Level Debugger,并不是low的调试器,而是轻量级的高性能调试器,xcode默认内置了它,因此我们不需要再自己安装。笔者最近也是系统的学习了LLDB的用法,在此之前就用过p和po
,哈哈😄。本篇文章主要为了将最近学习的LLDB记录并总结,加深记忆并方便以后查找。
另外,本篇文章主要有两部分,一个是LLDB的基础用法,另一个就是对基础LLDB使用插件进行扩展。
首先就来介绍一下这几个常用命令之间的关系吧。
先看看expression
指令。
expression
指令主要有以下作用:
expression a = 100
,同样这里你可以试试expression self.view.backgroundColor = [UIColor redColor]
,也是能修改背景颜色的。$
符号定义和使用lldb变量,如:expression int $b = 99
。可能是p太容易让人联想到print
了,很多人都会认为p是print的缩写,po是print object的缩写
,事实上并不是这样。
p 和 print
其实都是expression --
的缩写,可以使用help
指令查看。
po
也并不是print object(原本也就没这个写法)
的缩写,而是expression -O --
的缩写,同样可以使用help
指令查看。
help expression
看看expression -O --
是什么意思,如下,可以看到-O
代表的是对象的description(描述)
,即打印出变量的描述。
断点调试在日常开发中都经常用到,并且在xcode中我们也能够很轻易的设置、禁用、删除断点。下面就来看看如何使用LLDB达到并且超越界面化断点。
常用设置断点的参数及代表的意义。
缩写 | 全称 | 意义 |
---|---|---|
-f | --file | 文件名称 |
-l | --line | 行数 |
-n | --name | 方法名 |
-S | --selector | SEL |
-r | --func-regex | 方法正则 |
// 举例 breakpoint set -f ViewController.m -l 28 复制代码
click1:
的方法设置断点。breakpoint set -n click1: 复制代码
click2:
的方法设置断点。breakpoint set -S click2: 复制代码
click
的地方设置断点。breakpoint set -r click 复制代码
效果如下:
可以看到一次性设置了103个地方,显然想要的不是这样。条件拼接,和第一个例子那样。ViewController.m
文件中包含click
的地方设置断点。breakpoint set -f ViewController.m -r click 复制代码
breakpoint set
虽然在拼写时lldb会提示,但感觉还是太长了,怎么办?
直接使用b
即可。
// 缩写:br list breakpoint list 复制代码
这里需要注意以下,由于这三个断点是使用一条语句设置的,因此它们三个会被分到同一个断点组里面。
这一步就相当于界面操作中,让断点颜色变半透明。
代码设置如下:// 设置断点14.1无效 br disable 14.1 // 同样,将无效断点设置为有效 br enable 14.1 复制代码
br delete 14.3 复制代码可以看到,14.3并没有被删除,而只是被设置为了无效。原因就是14.3属于14这个组,不能只删除一个,要删必须将全部都删掉。
是不是感觉很666?
其实这些东西基本上用不到,哈哈哈。
正向开发中可以使用xcode提供的界面操作设置和删除断点。
而在逆向中,根本就获取不到这些符号(类名,文件名,方法名等)。
不信?看看下图:
上面说了,在逆向中,由于无法获取到符号,是无法直接通过符号设置断点的,而我们还需要使用断点怎么办?下内存断点。
这里在24行的断点处,获取了_name
指针的地址,然后通过
watchpoint set expression 0x000000014b80b880 复制代码
给_name
变量设置了一个内存断点,接下来c--->continue
过掉断点,点击按钮1,在_name = @"abc";
语句调用时,因_name
指向的空间变化了,就会打印出old value
和new value
。
variable
,效果是一样的,入下:
这里是有点取巧了,变量的内存地址也是直接通过符号获取的,并且这里也只是演示了给变量打内存断点,那么如何给方法打内存断点呢?
假设我现在要给click2:
方法打个断点,那么就需要这样计算:
上面我们查看MachO
文件的ASLR
时使用了这个命令。这里的image不是图片的意思,而是镜像。
可以理解为每一个MachO
都是一个image
,主程序是一个image
,主程序链接的每一个动态库也各自是一个image
。
image list
就是打印出App
中全部的image
信息,每个image
信息的那个地址就是这个image
在内存中的首地址,也即这个image
的ASLR
。
bt命令是用来查看函数调用栈的,如下,我在click1:
中调了click2:
,click2:
中又调用了click3:
,再在click3:
中设置一个断点,点击按钮1
,输入bt
命令,如下:
frame select [调用栈的编号] 复制代码
查看该调用栈的详细信息,包括调用者的内存地址,调用的方法,参数的内存地址等。
在此基础上,还能够使用up
和down
命令查看临近的调用栈信息。
这个就简单了,如下图:
上面记录的都是xcode自带的lldb所具有的功能,接下来要说的是使用插件对lldb进行扩展,使得lldb更简单,更强大。
对于chisel
的安装,最方便的还是使用Homebrew
安装了,使用mac电脑,安装一个Homebrew
是非常有用的,不过这个玩意因为是国外的服务器,所以安装更新都特别慢,甚至非常容易出错,一旦出错就要重来。对此,我们可以使用国内的源。
这里提供一种使用国内源进行安装的方法,终端执行下面的语句即可。如果使用的不是zsh,那么可以尝试将zsh改成bash。
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 复制代码
安装完Homebrew
之后,直接终端下面的命令即可安装chisel。
brew install chisel 复制代码
完成后在/Users/[username]/.lldbinit
文件里面添加一句(没有就创建一个文件)。
command script import /usr/local/Cellar/chisel/2.0.0/libexec/fblldb.py 复制代码
安装完chisel之后,可以来尝试一下chisel对lldb的扩展。
递归获取全部的视图类对象,并且按照视图的层级结构打印出来。
打印当前全部的控制器对象及层级关系。这里代码进行来一点修改,点击屏幕空白时跳转到NextViewController
。
ViewController
被NextViewController
盖住了,可以看到控制器对象的state
,ViewController 的 state 是 disappeared
。
用来刷新UI,在动态调试时,我们可能会修改UI控件的布局,此时直接使用caflush
即可刷新视图。
f-->find
,这两个命令是用来查找view
和ViewController
的。
这个就比较厉害了,直接输入taplog,然后你会发现程序正常运行了,此时点击任意一个按钮,那么就会打印出点击的这个按钮的信息。
这个对于逆向调试是非常有帮助的,直接定位到点击的那个控件的内存地址。有了内存地址,什么都好办了。打印出responder响应链。
打印对象所属的类的继承关系。
通过按钮的内存地址,直接找到按钮响应的actions。
打印对象所属类的全部方法以及属性。类似于class-dump
的功能。
让内存地址对应的控件在手机上闪烁一下。
让内存地址对应的控件变成半透明的红色,并且进入一个编辑模式,使用
还有其他的一些功能,使用help
命令可以进行查看。
这篇文章主要记录了
本文地址