Xcode的动态调试
简述
- 什么叫动态调试?
- 将程序运行起来,通过下断点、打印等方式,查看参数、返回值、函数调用流程等。
- Xcode的动态调试原理
- 通常我们使用的打断点、调试窗口输入命令打印等这些功能都是LLDB这个调试器的功能。
-
如下关系图
- MAC方面
- MAC上装有Xcode,Xcode内部嵌入有LLDB调试器
- 设备方面
- 设备上自带的有debugserver,专门用于对应LLDB的调试
- 调试过程
- Xcode调试窗口通过输入LLDB指令,给设备的debugserver
- debugserver接受到这个指令后执行到相应的APP上
- APP执行调试指令然后将执行的反馈信息返回给debugserver
- debugserver将这个信息反馈给LLDB
- LLDB将这个返回的信息,打印出来
- MAC方面
- GCC、LLVM、GDB、LLDB都是什么?
- GCC、LLVM都是编译器
- Xcode内置有编译器
- 打开项目-target->build setting ->搜索compiler(编译器)->能看见编译器就是LLVM
- 早期的Xcode(<4)内置的编译器是GCC,后来的就是LLVM
- GDB、LLDB都是调试器
- Xcode的调试器发展历程:GDB->LLDB
- GCC、LLVM都是编译器
- debugserver
- 一开始存放在MAC的Xcode中
- /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/De viceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver
- 当Xcode识别到手机设备时,Xcode会自动将debugserver安装到iPhone上,且仅仅在这个手机第一次调试时安装一次
- 位置: /Developer/usr/bin/debugserver
- Xcode调试的局限性
- 一般情况下,只能调试通过Xcode安装的APP
- 动态调试任意的APP(逆向破解学习)
- 从上面我们知道Xcode调试必须是先运行真机设备,而且保持Xcode一直运行在真机,然后才能调试
- 但是如果我想调试任意的APP呢?
- 。。。。。
LLDB指令
-
指令格式:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option- value]] [argument [argument...]]
- 其中,[] 代表可有可无
- command: 命令
- subcommand: 子命令
- action:命令操作
- options: 命令选项
- argument : 命令参数
-
比如给test函数设置断点:
breakpoint set -n test
- help
- 查看指令的用法
- 比如:help breakpointhelp、 breakpoint set
expression <cmd-options>--<expr>
- 执行一个表达式
<cmd-options>
:命令选项--
: 命令选项结束符,表示所有的命令选项已设置完毕,如果没有命令选项,–可以省略<expr>
:需要执行的表达式-
举例:
//在某个控制器代码处打个断点,然后在lldb输入一下命令,回车 expression self.view.backgroundColor = [UIColor redColor] //然后继续点击箭头,放开断点,就会发现项目执行了这句代码。
expression、expression--
和指令print、p、call
的效果一样expression-O--
和指令po
的效果一样。- 因此,综上所述,通常用
P/po
就可以了- P 通常跟表达式
- po 通常跟对象
- thread backtrace
- 打印线程的堆栈信息(其实就是函数调用栈)
- 和指令Bt效果一样
- 在某个函数中打个断点,然后输入改指令,回车
- 会出现一堆frame开头的东西
- 一个frame代表一个函数栈帧
- 可以看到,当前打断点的函数是由哪些函数一层一层调用过来的。
- thread return [返回的数据]
- 让函数直接返回某个值,不执行断点后面的代码
- frame variable [变量名]
- 打印当前栈帧的变量
- 不写变量名,打印出所有变量值
- 调试窗口上面的按钮对应的命令:
thread continue、continue、c
:程序继续运行thread step-over、next、n
:单步运行,把子函数当做整体,一步执行。hread step-in、step、s
:单步运行,遇到子函数会进入子函数。thread step-out、finish
:直接执行完当前函数的所有代码,返回到上一个函数
- 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函数名
- 内存断点:
- 在内存数据发生改变的时候触发
- 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 断点编号
- image lookup
- image:在iOS底层代表模块(Windows叫镜像)的意思
- Xcode代码中任意代码位置打一个断点,输入
image list
,就会打印出这个项目中加载的所有模块 - iOS程序如何跑起来的呢?
- 通过一个叫dyld的程序,将Mach-O可执行文件加载到内存中。
- 然后载入可执行程序所需要的动态库
- 这些所需要的动态库和当前的程序,我们称之为模块(image)
- image lookup -t 类型
- 比如:
image lookup -t NSInteger
- 查找某个类型的信息
-
上面的打印结果如下;
Best match found in /Users/mac/Library/Developer/Xcode/DerivedData/Testt-bevkcsprdibrnydobcyztythopas/Build/Products/Debug-iphonesimulator/Testt.app/Testt: id = {0x00009026}, name = "NSInteger", byte-size = 8, decl = NSObjCRuntime.h:12, compiler_type = "typedef NSInteger" typedef 'NSInteger': id = {0x0000906e}, name = "long int", qualified = "long", byte-size = 8, compiler_type = "long"
- 从上面
compiler_type = "long"
可以看出,NSIngeter这个类型实际编译的时候是long类型
- 比如:
- image lookup -a 地址
- 根据内存地址查找在模块中的位置
- 常用场合,在崩溃信息分析中比较常用
-
比如下面代码,造成的崩溃
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"=============="); NSArray *array = @[@"1",@"3"]; array[2]; }
-
崩溃结果如下:
2018-11-13 09:28:31.501097+0800 Testt[2649:46148] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 2 beyond bounds [0 .. 1]' *** First throw call stack: ( 0 CoreFoundation 0x0000000103f251bb __exceptionPreprocess + 331 1 libobjc.A.dylib 0x00000001034c3735 objc_exception_throw + 48 2 CoreFoundation 0x0000000103e714ec _CFThrowFormattedException + 194 3 CoreFoundation 0x0000000103fa7b00 +[__NSArrayI allocWithZone:] + 0 4 Testt 0x0000000102ba6685 -[ViewController touchesBegan:withEvent:] + 213 5 UIKitCore 0x0000000106e578e8 forwardTouchMethod + 353 6 UIKitCore 0x0000000106e57776 -[UIResponder touchesBegan:withEvent:] + 49 7 UIKitCore 0x0000000106e66dff -[UIWindow _sendTouchesForEvent:] + 2052 8 UIKitCore 0x0000000106e687a0 -[UIWindow sendEvent:] + 4080 9 UIKitCore 0x0000000106e46394 -[UIApplication sendEvent:] + 352 10 UIKitCore 0x0000000106f1b5a9 __dispatchPreprocessedEventFromEventQueue + 3054 11 UIKitCore 0x0000000106f1e1cb __handleEventQueueInternal + 5948 12 CoreFoundation 0x0000000103e8a721 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 13 CoreFoundation 0x0000000103e89f93 __CFRunLoopDoSources0 + 243 14 CoreFoundation 0x0000000103e8463f __CFRunLoopRun + 1263 15 CoreFoundation 0x0000000103e83e11 CFRunLoopRunSpecific + 625 16 GraphicsServices 0x000000010c5bb1dd GSEventRunModal + 62 17 UIKitCore 0x0000000106e2a81d UIApplicationMain + 140 18 Testt 0x0000000102ba6760 main + 112 19 libdyld.dylib 0x0000000105940575 start + 1 20 ??? 0x0000000000000001 0x0 + 1 )
-
从这个崩溃的函数调用栈,我们可以分析出,代码是崩溃在下面这一行
4 Testt 0x0000000102ba6685 -[ViewController touchesBegan:withEvent:] + 213
-
下面我跟就定位一下,在调试器中输入:
image lookup -a 0x0000000102ba6685
,回车,结果如下:Address: Testt[0x0000000100001685] (Testt.__TEXT.__text + 341) Summary: Testt`-[ViewController touchesBegan:withEvent:] + 213 at ViewController.m:38
- 从这个打印,我们可以看出,崩溃地方在ViewController.m的第38行
- image lookup -n 符号或者函数名
- 查找某个符号,或者函数的位置
- 比如:
image lookup -n test
-
打印如下:
1 match found in /Users/mac/Library/Developer/Xcode/DerivedData/Testt-bevkcsprdibrnydobcyztythopas/Build/Products/Debug-iphonesimulator/Testt.app/Testt: Address: Testt[0x0000000100001570] (Testt.__TEXT.__text + 64) Summary: Testt`test at ViewController.m:27 .....这些是其他地方有这个函数的打印,省略......
- 从上面可以看出,在Testt模块中有一个test函数,在
ViewController.m:27
行
- 从上面可以看出,在Testt模块中有一个test函数,在
- image list
- 列出所有加载的模块信息
- image list -o -f
- 打印出模块的偏移地址、全路径。
- 小技巧
- 点击enter,会自动执行上次的命令
- 绝大部分指令都可以使用缩写。