13 KiB
动态调试
Xcode 调试的原理
Xcode 是电脑端的程序,Xcode 使用 LLDB 进行调试。真机连接 Xcode 运行起来,点击屏幕,对应的事件处理方法里加了断点。手机是如何与 Xcode 断点连接同步的呢?
Xcode 编译器:GCC -> LLVM
Xcode 调试器:GDB -> LLDB
debugServer存放在:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/12.2/DeveloperDiskImage.dmg/usr/bin/debugserver- 当 Xcode 识别到手机设备时,Xcode 会自动将
debugserver安装到 iPhone 上。/Developer/usr/bin/debugserver - 一般情况下,Xcode 只可以调试通过 Xcode 安装的 App
动态调试任意 App
核心原因
上面说了 debugserver 只能调试 Xcode 连接安装的程序。这句话不够严谨,Xcode 连接 iPhone 的时候,会自动将 debugserver 安装到 iPhone 上,但是权限会做收敛。具体表现就是权限 plist。
所以我们可以自行修改权限,重新签名即可:
-
将
debugserver拷贝到电脑上 -
利用
ldid -e debugserver > debugserver.entitlements命令导出权限文件 -
打开
debugserver.entitlements添加get-task-allow、task_for_pid-allow2个权限<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>get-task-allow</key> <true/> <key>task_for_pid-allow</key> <true/> <key>com.apple.springboard.debugapplications</key> <true/> <key>com.apple.backboardd.launchapplications</key> <true/> <key>com.apple.backboardd.debugapplications</key> <true/> <key>com.apple.frontboard.launchapplications</key> <true/> <key>com.apple.frontboard.debugapplications</key> <true/> <key>seatbelt-profiles</key> <array> <string>debugserver</string> </array> <key>com.apple.diagnosticd.diagnostic</key> <true/> <key>com.apple.security.network.server</key> <true/> <key>com.apple.security.network.client</key> <true/> <key>com.apple.private.memorystatus</key> <true/> <key>com.apple.private.cs.debugger</key> <true/> </dict> </plist> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.springboard.debugapplications</key> <true/> <key>com.apple.backboardd.launchapplications</key> <true/> <key>com.apple.backboardd.debugapplications</key> <true/> <key>com.apple.frontboard.launchapplications</key> <true/> <key>com.apple.frontboard.debugapplications</key> <true/> <key>seatbelt-profiles</key> <array> <string>debugserver</string> </array> <key>com.apple.diagnosticd.diagnostic</key> <true/> <key>com.apple.security.network.server</key> <true/> <key>com.apple.security.network.client</key> <true/> <key>com.apple.private.memorystatus</key> <true/> <key>com.apple.private.cs.debugger</key> <true/> </dict> </plist> -
利用 ldid
ldid -S debugserver.entitlements debugserver进行重签
自动安装的 debugserver 存放目录为 Device/Developer/usr/bin 下的,但是这个目录是只读的。我们没办法将重签后的 debugserver 拖放到该位置。
但以后的使用场景是,在电脑终端 sh ~/login.sh 登录到手机后,在命令行模式下使用 debugserver AppProcessName,所以 debugserver 就需要安装(拖放 )到 Device/usr/bin
此时还是无法使用 debugserver ,需要修改权限 chmod +x /usr/bin/debugserver
debugserver 附加到某个 App 进程
debugserver *:端口号 -a 进程
*:端口号:使用 iPhone 的某个端口启动debugserver服务(只要不是保留端口号就可以)进程:输入 App 的进程信息(进程 ID 或者进程名称)
比如:debugserver *:10011 -a Wechat
Mac 上启动 LLDB,远程连接 iPhone 上的 debugserver
- 启动 LLDB,直接在终端输入
lldb - 连接 debugserver 服务:
process connect connect://手机 IP 地址:debugserver 服务端口号。其中connect://代表协议 - 第二步连接成功后,iPhone 的进程暂时就暂停了,下断点的状态,此时需要使用 LLDB 的 c 命令让程序先继续运行:
c - 接下来就用 LLDB 常规命令调试 App
通过 debugserver 启动 App
debugserver -x auto *:端口号 App 的可执行文件路径
LLDB 调试指令
指令格式
指令格式为:<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [arguments [argument...]]
<command>:命令<subcommand>: 子命令<action>:命令操作<options>:命令选项<arguments>:命令参数
比如给函数 sayHi 设置断点:breakpoint set -n sayHi,其中
- breakpoint 是
<command> - set 是
<action> - -n 是
<options> - sayHi 是
<arguments>
help 查看帮助
help <command> <subcommand>:用来查看某个指令和子指令 <command> <subcommand> 的说明。比如 help breakpoint set
expression
expression <cmd-options> -- <expr> 用于执行一个表达式
<cmd-options>:命令选项--:命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省了<expr>: 需要执行的表达式
比如经常在断点的时候,想额外执行某个函数或者处理某个逻辑,举个例子。在 touchesMoved 方法的断点模式下,想修改 view 的背景颜色,此时不需要重新运行。利用 expression 执行指令即可
(lldb) expression self.view.backgroundColor = .red
-
expression、expression --和指令 print、p、call 的效果一样 -
expression -O --和指令 po 效果一样。比如
堆栈信息
thread backtrace :打印线程的堆栈信息。效果等同于 bt
方法返回
thread return [<expr>] 让函数直接返回某个值,不会执行断点后面的代码
例如下面的代码,直接将函数的返回值进行修改了
frame variable
frame variable [<variable-name>] 打印当前栈帧变量
调试指令
thread continue、continue、c:程序继续运行
thred step-over、next、n :单步运行,把字函数当作整体一步执行
thread step-in、step、s:单步运行,遇到子函数会进入子函数
thread step-out、finish:直接执行完当前函数的所有代码,返回到上一个函数
si 、ni 和 s n:类似
-
sn是源码级别 -
si、ni是汇编指令级别
breakpoint
设置断点的指令
函数名
breakpoint set -n "函数名",但可能存在多个断点,因为同样方法名称的方法,都会被设置断点
比如 Person 类和 ViewController 类,都有 - (NSInteger)add:(NSInteger)a withB:(NSInteger)b 方法。 breakpoint set -n "add:withB:" 指令设置了2个断点。
当有多个方法同名的时候,只对当前类设置断点,指令格式为 breakpoint set -n "[类名 方法名]"。
比如通过 breakpoint set -n "[ViewController add:withB:]" 对 ViewController 类的 - (NSInteger)add:(NSInteger)a withB:(NSInteger)b 方法设置断点
如果一个方法没有参数,也可以通过 breakpoint set -n sayHi 的方式设置断点
函数地址
在逆向,调试别人的 App 的时候我们无法知道函数名称,所以给函数地址打断点就很重要了。
breakpoint set -a 函数地址。注意:函数地址是需要处理的,因为 iOS 有 ASLR 技术。
正则表达式
breakpoint set -r 正则表达式,效果就是给所有函数名符合正则表达式的函数,设置断点。
动态库
breakpoint set -s 动态库 -n 函数名
列出所有断点
breakpoint list 用于列出所有的断点,每个断点都有自己的编号
断点的删除、禁用、开启
breakpoint disable 断点编号 ,比如 breakpoint disable 2.1 2.2 禁用了2个断点
breakpoint delete 断点编号,比如 breakpoint delete 2。
比较奇怪,断点开启、禁用是可以跟子序号的,比如2.1 2.2,而断点删除必须是一级序号
断点指令信息
breakpoint command add 断点编号,该指令会给断点预先设置需要执行的命令,到触发断点时,就会按照指令添加的顺序执行。
指令可以添加多个,最后以 "DONE" 结束。
breakpoint command list 断点编号 用于查看该断点下的所有指令
breakpoint command delete 断点编号 用于删除该断点下的所有指令
内存断点
在内存数据发生改变时触发
watchpoint set variable 变量
在 viewDidLoad 中 通过 watchpoint set variable self->_age 给 age property 设置了断点。当改变的时候就触发断点
watchpoint set expression 变量地址
在 viewDidLoad 中 通过 watchpoint set expression 0x00007fcd20306a60 给 age property 设置了断点。当改变的时候就触发断点
watchpoint list
watchpoint disable 断点编号
watchpoint enable 断点编号
watchpoint disable 断点编号
watchpoint delete 断点编号
watchpoint command add 断点编号
watchpoint command list 断点编号
watchpoint command delete 断点编号
image
image list
image list 列举所加载的模块信息
image list -o -f 打印出模块的偏移地址、全路径
image lookup
image lookup -t 类型:查找某个类型的信息
image lookup -a 地址:根据内存地址查找在模块中的位置
举例:声明一个数组,只有5个元素,但通过下标6来访问数组的时候 crash 了,假设我们代码很长,crash 后想知道具体是哪一行代码造成了 crash,怎么办呢?
我们项目叫做 Demo111,那么 crash 堆栈中第4行有个地址 0x000000010d534dfc,可以通过该地址来分析具体的 crash 位置。通过
image lookup -a 0x000000010d534dfc 可以知道 Summary: Demo111 -[ViewController touchesBegan:withEvent:] + 108 at ViewController.m:29:18 是在 ViewController 的29行处发生了 crash。
image lookup -n 符号或者函数名:查找某个符号或者函数的位置