前言
之前,我们在探索动画及渲染相关原理的时候,我们输出了几篇文章,解答了
iOS动画是如何渲染,特效是如何工作的疑惑。我们深感系统设计者在创作这些系统框架的时候,是如此脑洞大开,也深深意识到了解一门技术的底层原理对于从事该方面工作的重要性。因此我们决定
进一步探究iOS底层原理的任务,本文探索的底层原理围绕“LLDB【命令结构、查询命令、断点设置、流程控制、模块查询、内存读写、chisel插件】”展开
一、概述
工欲善其事必先利其器,我们要探索iOS的底层原理,需要掌握一定的前知识,如:
- 编译器项目-LLVM
- Xcode调试器-LLDB
- iOS汇编-ARM、x86
本文的核心目标就是对LLDB进行简单介绍 且 对其常用的命令进行适当的实践
1.什么是LLDB?
在LLDB官网上介绍LLDB如下:
- LLDB是
Xcode在macOS上的默认调试器,支持在桌面、iOS设备和模拟器上调试C、Objective-C和c++。 - LLDB 是作为一组可重用组件构建的,高度利用了较大的LLVM项目中的现有库,如Clang表达式解析器和LLVM反汇编器。
- LLDB 是一个有着 REPL 的特性和 C++ 、Python 插件的开源调试器
2.LLDB 命令结构
<command> [<subcommand> [<subcommand>...]] <action> [-options [option- value]] [argument [argument...]]
对应:
- 1
<command>: 命令 - 2
<subcommand>: 子命令 - 3
<action>: 命令操作 - 4
[-options [option- value]]:命令选项 - 5
[argument [argument...]]:命令参数`
其中:
- command、subcommand:LLDB调试命令的名称
- 命令和子命令按层级结构来排列:
- 一个命令对象为跟随其的子命令对象创建一个上下文
- 子命令又为其子命令创建一个上下文,依此类推。
- 命令和子命令按层级结构来排列:
- action:命令操作,想在前面的命令序列的上下文中执行的一些操作。
- options:命令选项,行为修改器(action modifiers)。通常带有一些值。
- argument:命令参数,根据使用的命令的上下文来表示各种不同的东西。
- []:表示命令是可选的,可以有也可以没有
示例:
命令:breakpoint set -n main 对应到上面的语法就是:
- command:breakpoint 断点命令
- action:set 设置断点
- option:-n 表根据方法 name 设置断点
- arguement:mian 表示方法名为 mian
3.LLDB原始命令
LLDB支持不带命令选项的原始命令
-
原始命令会将命令后面的所有东西当做参数(arguement)处理
-
但很多原始命令
也可以带命令选项,当你使用命令选项的时候,需要在命令选项后面加--区分命令选项和参数。 如:expression(就是p/print/call)、expression -o(就是po),打印一个UIView对象地址:
前者是计算其地址的值,后者调用了对象的 description 方法,其中体现了唯一匹配原则:
- 假如根据前n个字母已经能唯一匹配到某个命令
- 则只写前n个字母等效于写下完整的命令
- 再用设置断点的命令举例,下面两条命令等效:
更多命令结构的介绍及用法,请参考 LLDB 入门。
接下来介绍点 LLDB 常用命令
4.LLDB查询命令
4.1 apropos
当我们并不能完全记得某个命令的时候,使用 apropos 通过命令中的某个关键字就可以找到所有相关的命令信息。
- 比如: 我们想使用stop-hook的命令,但是已经不记得stop-hook命令是啥样了:
我们也可以查看官方文档的介绍: LLDB command map。
4.2help指令 查询 指令用法
除了 apropos、查看官方网文档LLDB command map,我们也可以通过 help命令 快速查找 LLDB指令的用法,示例如下:
help breakpoint
help breakpoint set
二.断点设置指令|breakpoint、watchpoint
通过调试器断点指令操作断点:罗列 断点列表、使断点 失效/生效、删除/设置 断点
1.breakpoint 相关指令
breakpoint(可简写成br)
1.1 breakpoint set
= br set
设置断点
br set -a 函数地址:给函数设置断点br set -n 函数名:给函数设置断点breakpoint set -n test:给全局的test方法设置断点breakpoint set -n touchesBegan:withEvent::给全局的touchesBegan:withEvent:方法设置断点breakpoint set -n "-[ViewController touchesBegan:withEvent:]":给ViewController的touchesBegan:withEvent:方法设置断点breakpoint set -r 正则表达式给正则表达式匹配上的方法设置断点- 此处跟上正则表达式,会将所有匹配到的方法
都加上断点
- 此处跟上正则表达式,会将所有匹配到的方法
breakpoint set -s 动态库 -n 函数名:将指定动态库的指定函数打上断点- 其它: breakpoint 支持按文件名、函数名、行数、正则等各种条件筛选设置断点。详情参考官方文档:
示例:
br set -r testSel:遍历整个项目中包含 testSel 这个字符的所有方法并设置断点
1.2 breakpoint list
= br list :列出所有的断点,每个断点都有单独的编号
1.3 breakpoint disable 断点ID
= br disable 断点ID : 禁用断点
1.4 breakpoint enable 断点ID
= br enable 断点ID : 启用断点
1.5 breakpoint delete 断点ID
= br delete 断点ID : 删除断点
1.6 breakpoint command add 断点ID
= br command add 断点ID : 给指定断点编号的断点预先设置需要执行1条或者多条命令,到触发断点时,就会按顺序执行预先设置的命,设置多条命令时,用Done表示设置结束
示例: 假设我们需要在ViewController的viewDidLoad中查看self.view的值 我们首先给-[ViewController viewDidLoad]添加一个断点
可以看到添加成功之后,这个breakpoint的id为3,然后我们给他增加一个命令:po self.view
-o完整写法是–one-liner,表示增加一条命令。3表示对id为3的breakpoint增加命令。 添加完命令之后,每次程序执行到这个断点就可以自动打印出self.view的值了
如果我们一下子想增加多条命令,比如我想在viewDidLoad中打印当前frame的所有变量,但是我们不想让他中断,也就是在打印完成之后,需要继续执行。我们可以这样玩:
输入breakpoint command add 5:对 断点编号为5的断点 增加命令。它会让我们输入增加哪些命令,输入’DONE’表示结束。这时候我们就可以输入多条命令了
1.7 breakpoint command list 断点编号
= br command list 断点ID :查看某个编号的断点所有预先设置的命令
1.8 breakpoint command delete 断点编号
= br command delete 断点ID:删除指定编号断点的所有预设命令
2. 内存断点watchpoint
watchpoint 可简写为 wa
给指定的内存下断点,当内存中的数据发生改变时会触发
2.1 watchpoint set variable 变量
= wa set variable 变量:对指定的变量设置内存断点,当变量值改变的时候会触发
2.2 watchpoint set expression 内存地址
= wa set expression 内存地址:对指定内存地址设置断点,作用和watchpoint set variable相同
2.3 watchpoint list
= wa disable list:列出所有的内存断点
2.4 watchpoint disable 断点ID
= wa disable 断点ID:禁用内存断点
2.5 watchpoint enable 断点ID
= wa enable 断点ID:启用内存断点
2.6 watchpoint delete 断点ID
= wa delete 断点ID:删除内存断点
2.7 watchpoint command add 断点ID
= wa command add 断点ID:给指定断点编号的内存断点预先设置需要执行的命令,到触发内存断点时,就会按顺序执行预先设置的命令
2.8 watchpoint command list 断点编号
= wa command list 断点ID:查看某个编号的内存断点所有预先设置的命令
2.9 watchpoint command delete 断点编号
= wa command delete 断点ID:删除指定编号内存断点的所有预设命令
3.target stop-hook
target stop-hook add -o "frame variable":添加每次程序 stop 时都希望执行的命令:frame variable(打印当前栈内的所有变量)- target stop-hook、watchpoint 的增删改查命令与 breakpoint 的基本相同
4.其它断点
其它断点玩法需自定义插件支持,在第五段介绍。
三.流程控制
Xcode断点流程控制按钮 如图:
以下指令从左到右依次表示:指令全称、指令简称、极简指令:
-
第一个按钮:= 指令
process continue/continue/c: 让程序跳过断点继续运行 -
第二个按钮:单步运行,将子函数当做整体一步执行
- 源码级别 调试: =
thread step-over/next/n - 汇编级别 调试: =
thread step-inst-over/nexti/ni
- 源码级别 调试: =
-
第三个按钮:单步运行,遇到子函数会进入子函数
- 源码级别 调试: =
thread step-in/step/s - 汇编级别 调试: =
thread step-inst-over/stepi/si
- 源码级别 调试: =
-
第四个按钮:
thread step-out/finish: 退出当前帧栈
si、ni和s、n指令类似,但是s、n是源码级别,si、ni是汇编指令级别。
- 每一句OC代码会有一条或多条汇编指令构成
- s、n指令表示
一步一步执行每一句OC代码- 而si、ni表示
一步一步执行汇编指令
其他命令:
thread return:它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。-
这意味这函数剩余的部分不会被执行。
-
这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。
-
但是在函数的开头执行这个命令,是个非常好的隔离目标函数、伪造返回值的方式。
-
四.image模块查询指令(模块:可执行文件&共享库等)
这些命令在逆向及定位错误时使用频率非常高。
4.1 image list
image list: 列出所有所加载的模块信息
(lldb) image list
[ 0] 927601BD-5A00-319C-B8AF-8D501BB5D849 0x0000000100c01000 /Users/pinba/Library/Developer/Xcode/DerivedData/LLDBDemo-dkmvkvkytmalsoensevrvixujugm/Build/Products/Debug-iphonesimulator/LLDBDemo.app/LLDBDemo
[ 1] EEA931D0-403E-3BC8-862A-CBA037DE4A74 0x000000010d352000 /usr/lib/dyld
[ 2] 75369F31-702D-364A-95C3-8AFA9DD4B3A2 0x0000000100c0d000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim
......
image list -o -f: 打印出模块的偏移地址、全路径
lldb) image list -o -f
[ 0] 0x0000000000c01000 /Users/pinba/Library/Developer/Xcode/DerivedData/LLDBDemo-dkmvkvkytmalsoensevrvixujugm/Build/Products/Debug-iphonesimulator/LLDBDemo.app/LLDBDemo
[ 1] 0x000000010d352000 /usr/lib/dyld
[ 2] 0x0000000100c0d000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim
......
4.2 image lookup
image lookup --address 内存地址: 根据内存地址查找在模块中的位置- =
image lookup -a 内存地址
- =
(lldb) image lookup -a 0x00000001088b7cdb
Address: LLDBDemo[0x0000000100000cdb] (LLDBDemo.__TEXT.__text + 187)
Summary: LLDBDemo`-[ViewController touchesBegan:withEvent:] + 123 at ViewController.m:26:15
image lookup -v --address 内存地址:查找完整的源代码行信息- =
image lookup -v -a 内存地址
- =
image lookup --type 类型:查找某个类型的信息- =
image lookup -t 类型
- =
(lldb) image lookup -t NSUInteger
Best match found in /Users/pinba/Library/Developer/Xcode/DerivedData/LLDBDemo-dkmvkvkytmalsoensevrvixujugm/Build/Products/Debug-iphonesimulator/LLDBDemo.app/LLDBDemo:
id = {0x1000000ab}, name = "NSUInteger", byte-size = 8, decl = NSObjCRuntime.h:13, compiler_type = "typedef NSUInteger"
typedef 'NSUInteger': id = {0x1000000b8}, name = "long unsigned int", qualified = "unsigned long", byte-size = 8, compiler_type = "unsigned long"
image lookup -n 符号或函数名: 查找某个符号或者函数的位置
(lldb) image lookup -n "-[ViewController touchesBegan:withEvent:]"
2 matches found in /Users/pinba/Library/Developer/Xcode/DerivedData/LLDBDemo-dkmvkvkytmalsoensevrvixujugm/Build/Products/Debug-iphonesimulator/LLDBDemo.app/LLDBDemo:
Address: LLDBDemo[0x0000000100000c60] (LLDBDemo.__TEXT.__text + 64)
Summary: LLDBDemo`-[ViewController touchesBegan:withEvent:] at ViewController.m:22 Address: LLDBDemo[0x0000000100000c60] (LLDBDemo.__TEXT.__text + 64)
Summary: LLDBDemo`-[ViewController touchesBegan:withEvent:] at ViewController.m:22
五、expression指令
expression指令被用来执行一个表达式
expression self.view.backgroundColor = [UIColor redColor]
//或者
expression -- self.view.backgroundColor = [UIColor redColor]
expression- =
print、p、call效果等同
- =
expression -o- =
po效果等同
- =
(lldb) expression -o -- self.view
<UIView: 0x7f8f77c1dde0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x600002098d20>>
(lldb) po self.view
<UIView: 0x7f8f77c1dde0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x600002098d20>>
expression后的 -- 表示命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省略。如果expression之后有命令选项,则--不能省略。
六.内存读写指令
1.格式
- x是16进制
- f是浮点
- d是10进制
2.字节大小
- b:byte 1字节
- h:half word 2字节
- w:word 4字节
- g:giant word 8字节
3.读取内存
格式:memory read/数量格式字节数 内存地址
- x/数量-格式-字节大小 内存地址
- x/3xw 0x10010
4. 修改内存中的值
格式:memory write 内存地址 数值
- memory write 0x0000010 10
七.寄存器相关的指令
- register read:显示当前线程的通用寄存器。
- register write rax 123:将一个新的十进制值“123”写入当前线程寄存器“rax”。
八.其他常用指令
8.1 thread backtrace
= bt :指令的作用是打印线程的堆栈信息
8.2 frame variable
打印当前栈帧的变量
(lldb) frame variable
(ViewController *) self = 0x00007f8f7b0091e0
(SEL) _cmd = "touchesBegan:withEvent:"
(__NSSetM *) touches = 0x00006000020c13e0 1 element
(UITouchesEvent *) event = 0x00006000011c43c0
(lldb) frame variable self
(ViewController *) self = 0x00007f8f7b0091e0
8.3 LLDB小技巧
- 每次敲Enter键,都会自动执行上次的命令
- 绝大部分的指令都可以使用缩写
(lldb) breakpoint list
(lldb) br li
(lldb) br l
(lldb) breakpoint set -n test
(lldb) br s -n test
九.LLDB第三方插件
有一些公司(如Facebook)专门为LLDB写了一些插件,便于提供给开发者更多便捷的调试
推荐插件: facebook 开源的 LLDB 插件 chisel
快捷命令: 它提供的快捷 命令清单及说明
9.1 安装
- 未安装Homrebrew,先安装Homrebrew 参考MAC上Homebrew常用命令
- 安装Chisel
brew install chisel
9.2 配置
- 如果没有创建 .lldbinit 文件,则在终端创建文件
$ cd ~
$ touch .lldbinit
//open .lldbinit
- 编辑 .lldbinit 文件
- 1、找到
chisel的安装路径$ brew list chisel - 2、找到
fbchisellldb.py的路径/opt/homebrew/Cellar/chisel/2.0.1/libexec/fbchisellldb.py - 3、编辑
$ vim ~/.lldbinit, 并添加以下内容:command script import /opt/homebrew/Cellar/chisel/2.0.1/libexec/fbchisellldb.py
- 1、找到
9.3 使用
最后wq保存,重启Xcode,就可以使用Chisel了。
9.4 快捷命令
| 命令 | 命令描述 | iOS | OS X |
|---|---|---|---|
| pviews | Print the recursive view description for the key window. | YES | YES |
| pvc | Print the recursive view controller description for the key window. | YES | NO |
| visualize | Open a UIImage, CGImageRef, UIView, CALayer, NSData (of an image), UIColor, CIColor, or CGColorRef in Preview.app on your Mac. | YES | NO |
| fv | Find a view in the hierarchy whose class name matches the provided regex. | YES | NO |
| fvc | Find a view controller in the hierarchy whose class name matches the provided regex. | YES | NO |
| show/hide | Show or hide the given view or layer. You don't even have to continue the process to see the changes! | YES | YES |
| mask/unmask | Overlay a view or layer with a transparent rectangle to visualize where it is. | YES | NO |
| border/unborder | Add a border to a view or layer to visualize where it is. | YES | YES |
| caflush | Flush the render server (equivalent to a "repaint" if no animations are in-flight). | YES | YES |
| bmessage | Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method. | YES | YES |
| wivar | Set a watchpoint on an instance variable of an object. | YES | YES |
| presponder | Print the responder chain starting from the given object. | YES | YES |
| ... | ... | ... | ... |
总结
通过通篇介绍,我们了解了:
- LLDB命令结构
- 查询命令
- 断点设置
- 流程控制
- 模块查询
- 内存读写
- 第三方LLDB插件:chisel
专题系列文章
1.前知识
- 01-探究iOS底层原理|综述
- 02-探究iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探究iOS底层原理|LLDB
- 04-探究iOS底层原理|ARM64汇编
2. 基于OC语言探索iOS底层原理
- 05-探究iOS底层原理|OC的本质
- 06-探究iOS底层原理|OC对象的本质
- 07-探究iOS底层原理|几种OC对象【实例对象、类对象、元类】、对象的isa指针、superclass、对象的方法调用、Class的底层本质
- 08-探究iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 执行、关联对象
- 09-探究iOS底层原理|KVO
- 10-探究iOS底层原理|KVC
- 11-探究iOS底层原理|探索Block的本质|【Block的数据类型(本质)与内存布局、变量捕获、Block的种类、内存管理、Block的修饰符、循环引用】
- 12-探究iOS底层原理|Runtime1【isa详解、class的结构、方法缓存cache_t】
- 13-探究iOS底层原理|Runtime2【消息处理(发送、转发)&&动态方法解析、super的本质】
- 14-探究iOS底层原理|Runtime3【Runtime的相关应用】
- 15-探究iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探究iOS底层原理|RunLoop的应用
- 17-探究iOS底层原理|多线程技术的底层原理【GCD源码分析1:主队列、串行队列&&并行队列、全局并发队列】
- 18-探究iOS底层原理|多线程技术【GCD源码分析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探究iOS底层原理|多线程技术【GCD源码分析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探究iOS底层原理|多线程技术【GCD源码分析3:线程调度组dispatch_group、事件源dispatch Source】
- 21-探究iOS底层原理|多线程技术【线程锁:自旋锁、互斥锁、递归锁】
- 22-探究iOS底层原理|多线程技术【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探究iOS底层原理|内存管理【Mach-O文件、Tagged Pointer、对象的内存管理、copy、引用计数、weak指针、autorelease
3. 基于Swift语言探索iOS底层原理
关于函数、枚举、可选项、结构体、类、闭包、属性、方法、swift多态原理、String、Array、Dictionary、引用计数、MetaData等Swift基本语法和相关的底层原理文章有如下几篇:
其它底层原理专题
1.底层原理相关专题
2.iOS相关专题
- 01-iOS底层原理|iOS的各个渲染框架以及iOS图层渲染原理
- 02-iOS底层原理|iOS动画渲染原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏渲染原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决方案