什么叫动态调试?
将程序运行起来,通过下断点、打印等⽅式,查看参数、返回值、函数调⽤流程等。
一、Xcode的动态调试原理
Xcode内置了LLDB调试器,每次通过Xcode调试APP时,会启动iPhone中的debugserver(iPhone手机官方调试工具。用来协助调试APP)。
LLDB全称Low Level Debugger ,并不是低水平的调试器,而是轻量级的高性能调试器。
如果iPhone手机从未连接过Xcode,debugserver是不在手机上的。debugserver一开始存放在Mac的Xcode⾥面(/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/De viceSupport/10.0/DeveloperDiskImage.dmg/usr/bin/debugserver
)。当Xcode识别到⼿机设备时,Xcode会⾃动将debugserver安装到iPhone上(/Developer/usr/bin/debugserver
)。
当Xcode把APP运行起来开始调试时,LLDB会发送指令给debugserver,debugserver监听到指令后会把指令执行到APP,APP再把指令信息反馈给debugserver,debugserver反馈给LLDB,最终LLDB会把反馈信息打印到Xcode的控制台。
LLDB是通过数据线或TCP(同一个局域网)和debugserver进行交互的。
关于GCC、LLVM、GDB、LLDB:
Xcode调试的局限性:⼀般情况下,只能调试通过Xcode安装的APP。
二、动态调试任意APP
既然Xcode有一定的局限性,我们则可以通过终端运行LLDB调试APP。但是我们需要解决的问题就是建立LLDB和debugserver的连接通道,并且能让debugserver和APP进行通信。
2.1. 环境搭建
2.1.1. debugserver的权限问题
默认情况下,/Developer/usr/bin/debugserver
缺少一定的权限,只能调试通过Xcode安装的APP,⽆法调试其他APP(⽐如来自App Store的APP)。
如果希望调试其他APP,需要对debugserver重新签名,签上2个调试相关的权限(get-task-allow、task_for_pid-allow)。
2.1.2. debugserver重签权限
- iPhone上的
/Developer
⽬录是只读的,无法直接对/Developer/usr/bin/debugserver
⽂件签名,需要先把debugserver复制到Mac,通过ldid命令导出文件以前的签名权限。
1 | ldid -e debugserver > debugserver.entitlements |
- 给
debugserver.plist
⽂件加上get-task-allow
和task_for_pid-allow
权限。
- 通过ldid命令重新签名。
1 | ldid -Sdebugserver.entitlements debugserver |
将已经签好权限的debugserver放到
/usr/bin
目录,便于找到debugserver指令(更重要的是以前的目录文件是只读的,无法进行覆盖)。默认情况下,需要开启
/usr/bin/debugserver
的权限。
1 | chmod +x /usr/bin/debugserver |
关于权限的签名,也可以使用codesign:
1 | 查看权限信息 |
2.1.3. 让debugserver附加到某个APP进程
1 | debugserver *:端口号 -a 进程 |
*:端⼝号
:*
代表主机地址。端口号意思是使用iPhone的某个端口启动debugserver服务(只要不是保留端口号就⾏)。-a:进程
:输⼊APP的进程信息(进程ID或者进程名称)。
上面介绍的是debugserver连接运行时的APP,其实我们还可以通过debugserver启动APP:
1 | debugserver -x auto *:端⼝口号 APP的可执⾏行行⽂文件路路径 |
2.2. 建立连接
在Mac上启动LLDB,远程连接iPhone上的debugserver服务。
启动LLDB
1
lldb
连接debugserver服务(可以通过usbmuxd把debugserver的端口映射到Mac,然后填写映射的IP地址和端口号,最终使用USB进行传输)
1
(lldb) process connect connect://⼿机IP地址:debugserver服务端口号
默认情况下,只要debugserver服务连接成功,对应的APP就会处于暂停状态(类似下了一个断点)。可以使用LLDB的c命令让程序继续运行
1
(lldb) c
三、常见的LLDB指令
指令的格式:
1
<command> [<subcommand> [<subcommand>...]] <action> [-options [option- value]] [argument [argument...]]
command
:命令subcommand
:子命令action
:命令操作options
:命令选项argument
:命令参数- ⽐如给
test
函数设置断点1
breakpoint set -n test
- breakpoint是
command
- set是
subcommand
- -n是
options
- test是
argument
- breakpoint是
**help
<command>
**:查看指令的⽤法(⽐如help breakpoint
、help breakpoint set
)**expression
<cmd-options> -- <expr>
**:执行⼀个表达式cmd-options
:命令选项--
:命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--
可以省略expr
:需要执行的表达式- 比如设置view的背景色:
1
expression self.view.backgroundColor = [UIColor redColor]
expression
、expression --
和指令print
、p
、call
的效果⼀样expression -O --
(-O
就是--object description
的指令缩写,类似NSLog(@"%@", object)
)和指令po
的效果⼀样
thread backtrace:打印线程的堆栈信息(和指令bt的效果一样)
**thread return []**:让函数直接返回某个值,不会执⾏断点后面的代码
**frame variable []**:打印当前栈帧的变量
thread continue、continue、c:程序继续运⾏
thread step-over、next、n:单步运行,把⼦函数当做整体⼀步执⾏
thread step-in、step、s:单步运行,遇到子函数会进⼊子函数
thread step-out、finish:直接执行完当前函数的所有代码,返回到上⼀个函数
thread step-inst-over、nexti、ni:
thread step-inst、stepi、si:
si、ni和s、n类似:
- s、n是源码级别
- si、ni是汇编指令级别
breakpoint set:设置断点
- breakpoint set -a 函数地址
- breakpoint set -n 函数名
1
2
3breakpoint set -n test
breakpoint set -n touchesBegan:withEvent:
breakpoint set -n "-[ViewController touchesBegan:withEvent:]" - breakpoint set -r 正则表达式
- breakpoint set -s 动态库 -n 函数名
breakpoint list:列出所有的断点(每个断点都有⾃己的编号)
breakpoint disable 断点编号:禁⽤断点
breakpoint enable 断点编号:启⽤断点
breakpoint delete 断点编号:删除断点
breakpoint command add 断点编号:给断点预先设置需要执行的命令,到触发断点时,就会按顺序执⾏
breakpoint command list 断点编号:查看某个断点设置的命令
breakpoint command delete 断点编号:删除某个断点设置的命令
内存断点:在内存数据发生改变的时候触发
- watchpoint set variable 变量:
watchpoint set variable self->age
- watchpoint set expression 地址:
watchpoint set expression &(self->_age)
- watchpoint list
- watchpoint disable 断点编号
- watchpoint enable 断点编号
- watchpoint delete 断点编号
- watchpoint command add 断点编号
- watchpoint command list 断点编号
- watchpoint command delete 断点编号
- watchpoint set variable 变量:
image lookup:模块查找(image不是图片的意思)
- image lookup -t 类型:查找某个类型的信息
- image lookup -a 地址:根据内存地址查找在模块中的位置
- image lookup -n 符号或者函数名:查找某个符号或者函数的位置
image list:列出所加载的模块信息
image list -o -f
:打印出模块的偏移地址、全路径
四、ASLR
iOS4.3开始引入了ASLR技术。
什么是ASLR?Address Space Layout Randomization,地址空间布局随机化。是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。
在调试其他APP时,是无法通过函数名下断点的(breakpoint set -n 函数名
),而且通过函数地址下断点时(breakpoint set -a 函数地址
),函数地址需要经过计算才能得出。
4.1. 未使用ASLR
代码编译以后,虚拟内存地址基本都是固定的,__PAGEZERO
起始内存地址是0,其他人员可以非法入侵任意APP。
__PAGEZERO
是空指针指向的内存区域(安全),更多用途可自行查阅资料。
4.2. 使用了ASLR
ASLR控制的是__PAGEZERO
前面的一部分内存,随机产生的Offset
,让__PAGEZERO
的起始内存地址动态变化。
函数的内存地址(VM Address) = File Offset + ASLR Offset + __PAGEZERO Size
Hopper、IDA等工具中的地址都是未使用ASLR的VM Address。