logify、Xcode运行原理
- 1. 动态调试Xcode
- 1.1 Xcode的动态调用原理
- 1.2 动态调试任何APP
- 1.2.1 原理图和条件
- 1.2.2 debugserver的权限问题
- 1.2.3 让debugserver附加到某个APP进程
- 1.2.4 通过debugserver启动App
- 1.2.5 在Mac上启动LLDB,远程连接iPhone上的debugserver服务
- 1.2.5 可能遇到的问题(手机环境:ios 12.5.1)
- 2.1 LLDB命令格式
1. 动态调试Xcode
- 什么叫动态调试?将程序运行起来,通过下面断点,打印等方式,查看参数、返回值、函数调用流程等
1.1 Xcode的动态调用原理
-
关于GCC、LLVM、GDB、LLDB
Xcode的编译器发展:GCC -> LLVM
- Xcode的调试器发展:GDB -> LLDB
debugserver
一开始存放在Mac的Xcode里面
/Applications/Xcode12.3.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver
当Xcode识别手机设备时,Xcode会自动将
debugserver
安装到iPhone上,如果是从来没有真机测试的iPhone上是没有
debugserver
的:
Xcode调试的局限性:一般情况下,只能通过Xcode安装的APP
Xcode调试APP的原理图:
当你真机运行的时候,Xcode会在手机上安装
debugserver
的,
debugserver
监听着
LLDB
发送的一些指令,首先
LLDB
将这些指令传送给
debugserver
, 然后
debugserver
接收到这个指令时,在执行到
app
上,
app
执行指令后把结果反馈给
debugserver
,然后再由
debugserver
把结果返回给
LLDB
,在由
LLDB
把信息打印在
Xcode
上,这也是Xcode可以动态调试app的原因:
p
: 直接地址
po
:打印对象
LLDB 是通过数据线来传递数据的
1.2 动态调试任何APP
1.2.1 原理图和条件
动态调试任何App示意图:
- 首先手机上必须要有
debugserver
,其次我们可以通过终端 输入
lldb
进入
lldb
的调试环境:
1.2.2 debugserver的权限问题
- 默认情况下,
/Developer/usr/bin/debugserver
缺少一定的权限,只能通过Xcode安装的APP,无法连接其他APP(比如来自App Store的App)
- 如果希望调试其他APP,需要对
debuserver
重新签名,签上两个调试相关的权限
get-task-allow Boolean 类型 值为YES
-
task_for_pid-allow Boolean 类型 值为YES
debugserver
签上权限
- iPhone上的
/Developer
目录是只读,无法直接对
/Developer/usr/bin/debugserver
文件直接签名,需要先把
debugserver
赋值到Mac上
ldid
导出以前文件以前的签名权限:
ldid -e debugserver > debugserver.entitlements
debugserver.entitlements
文件加上
get-task-allow、t20000ask_for_pid-allow
权限:
ldid
命令重新签名:
did -Sdebugserver.entitlements debugserver
/usr/bin
目录,便于找到debugserver指令
codesign
# 查看权限信息codesign -d --entitlements - debugserver# 签名权限codesign -f -s - --entitlements debugserver.entitlements debugserver# 或则缩写codesign -fs- --entitlements debugserver.entitlements debugserver
1.2.3 让debugserver附加到某个APP进程
-
我们可以再终端查看
debugserver
的指令:
-
debugserver *:端口号 -a 进程
*:端口号
:使用iphone的某个端口启动debugserver服务(只要不是保留端口号就行,就和我们使用SSH登录把iphone的22端口映射到本机的10010一样)
- 进程:输入APP的进程信息(进程ID和进程名称)
1.2.4 通过debugserver启动App
debugserver -x auto *:端口号 App的可执行文件路径
1.2.5 在Mac上启动LLDB,远程连接iPhone上的debugserver服务
-
启动LLDB:
lldb
-
连接debugserver服务:
process connect connect://手机IP地址:debugserver服务端端口号
-
使用LLDB的
c
命令让程序先继续运行:
c
-
手机端:
-
电脑端:
刚开始是卡主的,我们输入c
指令App才可以正常运行
1.2.5 可能遇到的问题(手机环境:ios 12.5.1)
-
failed to attach to process named: “”
: 找不到该进程的名称,确认进程名称重新输入
-
Failed to get connection from a remote gdb process
:
-
解决方法:
使用的端口可能被占用, 换一个端口试试
-
debugserver
的权限文件中包含以下权限(删除该权限即可):
rejecting incoming connection from ::ffff:127.0.0.1
:
出现这种情况指令:
手机端:
debugserver *:10012 -a neteasemusic
电脑端:
process connect connect://localhost:10012
或则
process connect connect://127.0.0.1:10012
解决方案:
手机端:
debugserver 127.0.0.1:10012 -a neteasemusic
电脑端:
process connect connect://127.0.0.1:10012
暂时还没有找到其它的解决方案
2. LLDB
2.1 LLDB命令格式
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-Value]] [argument [arguments...]]
-
<command>
:命令
-
<subcommand>
:子命令
-
<action>
:命令操作
-
<options>
:命令选项
-
<argument>
: 命令参数
- 比如给test函数设置断点:
breakpoint set -n test
:
breakpoint
是
<command>
-
set
是
<action>
-
-n
是
<options>
-
test
是
<agrument>
通过上图,当我们过掉
touchesBegan:withEvent:
方法中断点时,会进入到
test
函数中的断点(通过LLDB指令添加的断点)
2.2 help
-
help<command>
: 查看指令的用法,比如
help breakpoint、help breakpoint set
2.3 expression
-
expression<cmd-options>--<expr>
: 执行一个表达式
<cmd-options>
:命令选项
-
--
:命令选项结束符,表示所有命令选项已经设置完毕,如果没有命令选项,
--
可以省略
-
<expr>
:需要执行的表达式
expression self.view.backgroundColor = [UIColor redColor]
有时候我们在通过打断点形式调试程序的时候,特别想动态的让这个程序动态的执行某段代码,以前我们是先把程序退掉,然后把要执行的代码加上,在重新运行程序,那么
expression
指令帮们我们实现这个目的:
由上图可知,我们可以直达直接访问view的
backgroundColor
的会报错,但是我们知道每一个view都有一个CALayer,我们可以直接访问这个属性,所以最终需改背景色的表达式:
expression self.view.layer.backgroundColor = [UIColor redColor].CGColor
-
expression、expression --
和指令
print、p、call
的效果一样
-
expression -O --
和指令
po
效果一样
-
p
是直接打印对象的地址,但是
po
相当于
NSLog
一样的效果:
2.3 thread xxx
-
thread backtrace
: 打印线程的堆栈信息,和指令
bt
的效果一样,示例:比如我们想知道test函数是谁调用的:
-
thread return [<expr>]
;让函数直接返回某个值,不会执行断点后面的代码
-
frame variable [<variable-name>]
:打印当前栈帧的变量,(
frame(栈帧)
一个函数调用就是一帧)
-
thread continue、continue、c
:程序继续运行
-
thread step-over、next、n
:单补运行,把子函数当做整体一步执行
-
thread step-in、step、s
:单步运行,遇到子函数会进入函数
-
thread step-out、finish
:直接执行完当前函数的所有代码,返回到上一个函数
上述四个指令对应着Xcode工具的四个指令:
注意:在一行代码一行代码的情况下 这两个指令效果相同, 如果是执行函数的时候,
setp over
是直接过掉整个函数 而
setp into
会进入函数内部一步一步的走;如果进入函数内部了, 执行
setup out
会
瞬间执行完函数代码
,并且回到函数调用的时候, 相当于函数已经调用完了
-
thread step-inst-over 、nexti 、ni
:汇编指令级别的单步运行,把一个汇编函数当做一个整体一步执行
-
thread step-inst 、stepi、si
:汇编指令级别的单步运行,遇到子函数会进入子函数,一行汇编指令的执行
-
si、ni
和
s、n
类似:前者是汇编指令级别,后者是源码级别
2.4 breakpoint
-
breakpoint set
:设置断点
breakpoint set -a 函数地址
-
breakpoint set -n 函数名
breakpoint 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 断点编号
:删除某个断点设置的命令
首先查看所有断点, 然后给第二个断点添加需要执行命令, 在出发第二个断点时,就回按照顺序执行预设置的命令
注意: 在给断点设置完预设置命令时,需要输入DONE来结束输入命令。
2.5 内存断点
-
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 断点编号
我们给
_count
打了一个断点,当我们修改
_count
的值时候,就会进入断点
2.6 image(模块)
-
image lookup -t 类型
:查找某个类型的信息,不用进入头文件,可以快速的查看一些类的信息
-
image lookup -a 地址
:根据内存地址查找在模块中的位置,对于崩溃信息的定位有比较好的作用
-
image lookup -n 符号或者函数名
:查找某个符号或则函数的位置
-
image list
列出锁加载的模块的信息
-
image list -o -f
:打印出模块的偏移地址、全路径
可以运行app当前的一些模块 ,我们自己的app本身也是一个模块,app依赖的动态库也是一个模块
2.7 小技巧
- 敲
Enter
,会自动执行上次的命令
- 绝大部分指令都可以使用缩写