LLDB 命令库 HMLLDB 介绍
中文版介绍的更新有延迟,建议优先查看GitHub里的README。
2024年7月更新内容
新增:twos_complement_to_int
更新:environment、reference、edisassemble、bpframe、cbt、adrp
2024年1月更新内容
新增:autodsym,trace-step-over-instruction,adrp,cbt,edisassemble,reference
更新:rr,fclass,fsubclass,fmethod,ivarsinfo,methods,bpframe
2023年3月更新内容
新增命令:ivarsinfo、bpframe、bpmessage、bpmethod、rc、rr、tracefunction、traceinstruction、pfont
前言
和大多数人一样,我首次接触到的LLDB命令是其自带的po
命令,用于打印对象。后来我学习了更多的命令才发觉LLDB的强大,尤其是可用于动态修改的expression
命令,我至今依然在大量使用,如自动填充账号密码,log控制,mock控制,自动打印UIViewController的生命周期等等。
不限于此,苹果还提供了Scripting Bridge API
,简称SB API
,可用Python脚本控制LLDB,其中Facebook的chisel项目和Derek Selander的LLDB项目是比较知名LLDB命令库,其中大部分功能都是基于SB API
。不过这两个项目还存在很多问题,对我影响较大的就是更新不及时和命令互斥,另外我自己也有一些想法,于是也开发了一个LLDB命令库,即HMLLDB,首要目的是提升开发和调试效率。
特色
- 无侵入,绿色版,项目无需修改,git看不到任何记录
- 所有命令均支持真机,大部分命令支持模拟器
- 所有命令均支持Objective-C和Swift项目
- 一些命令提供了应用内的交互式的UI
系统要求
- Xcode 15.4
- 64位模拟器或真机,iOS 13.0 +
- 部分命令需要Debug模式(或者Optimization Level设置为[-O0]/[-Onone])
安装
- 下载源码,强烈建议clone仓库,方便pull代码保持最新的版本。一些命令很可能要随着Xcode版本的更新而调整,我都会及时适配。
- 打开(或创建)
~/.lldbinit
文件(".lldbinit"是完整的文件名,没有多余后缀,放在当前的用户目录下,即~),在末尾新增这一行命令:
command script import <path>
其中<path>
是项目里HMLLDB.py
这个文件的绝对路径,比如我电脑这一行的是:
command script import /Users/pal/Desktop/gitProjects/HMLLDB/commands/HMLLDB.py
- 重启Xcode,运行你自己的iOS项目,点击
Pause program execution
进入LLDB调试模式,输入命令help
,如果看到有下文介绍的命令,表明安装成功。
注意:如果你配置了基于项目Scheme的LLDB Init File,则可能需要在那个文件新增import。
命令介绍
主要命令的简要说明:
Command | Description |
---|---|
autodsym | 给当前模块添加调试符号文件 |
deletefile | 删除沙盒里指定的文件 |
pbundlepath | 打印主bundle的路径 |
phomedirectory | 打印沙盒的路径,即"~" |
fclass | 查找并打印所有包含指定字符串的类名 |
fsubclass | 查找并打印一个类的所有子类 |
fsuperclass | 查找并打印一个类的父类 |
fmethod | 在所有方法列表中查找并打印指定方法,也可以打印指定类的方法列表 |
methods | 相当于调用[input _methodDescription] ,打印类的所有方法 |
properties | 相当于调用[input _propertyDescription] ,打印类的所有property |
ivars | 相当于调用[input _ivarDescription] ,打印类实例的ivar |
ivarsinfo | 打印指定类的ivars的信息 |
bpframe | 设置一个断点,只有匹配指定堆栈/地址时才暂停 |
bpmessage | 给指定类的指定方法设置一个断点,即使这个方法是其父类的实现也有效 |
bpmethod | 设置一个断点,当前线程执行到下一个OC方法的实现时暂停(必须是通过objc_msgSend调用的方法) |
cbt | 基于fp/lr寄存器,完整展示当前线程调用栈 |
rr | lldb自带register read 命令的别名,新增选项打印栈寄存器sp指向地址的值 |
twos_complement_to_int | 把补码转换为有符号数 |
reference | 扫描machO获取指定地址所有的引用信息 |
adrp | 快速获取adrp指令的计算结果 |
edisassemble | disassemble命令增强版本 |
tracefunction | 基于指令,一步步追踪函数跳转,直到命中下个断点停止 |
traceinstruction | 追踪指令,直到命中下个断点停止 |
trace-step-over-instruction | 以step-over的形式追踪指令 |
pfont | 打印设备支持的所有字体名称 |
plifecycle | 用于打印UIViewController的生命周期 |
redirect | stdout/stderr重定向 |
push | 找到一个UINavigationController,然后push一个指定的UIViewController |
showhud | 在keyWindow上展示一个debug视图,显示内存占用,CPU使用率,主线程FPS |
showfps | 已被showhud命令替代,仅供学习参考 |
sandbox | present一个沙盒浏览器,具有系统分享、删除文件功能 |
inspect | 查看当前页面的UIView对象。还能看到常见的类如UILabel的关键属性值 |
request | 自动打印http/https请求 |
environment | 用于诊断当前的环境 |
... |
和系统LLDB命令一样,表中所有命令均支持通过help <command>
查看语法和用例,例如help fmethod
输出如下:
(lldb) help fmethod
Find the method. Expects 'raw' input (see 'help raw-input'.)
Syntax: fmethod
Syntax:
fmethod <methodName> (Case insensitive.)
fmethod [--class] <className>
Options:
--class/-c; Find all method in the class
Examples:
(lldb) fmethod viewdid
(lldb) fmethod viewDidLayoutSubviews
(lldb) fmethod -c UITableViewController
This command is implemented in HMClassInfoCommands.py
例子
接下来我将介绍命令具体用法。其中有演示效果的,来源于开源项目Kingfisher里的demo,没有改动一行代码。这些例子用于文章介绍,实际使用的时候,用help <command>
命令查找用法更方便。
建议点击Pause program execution
主动进入LLDB调试模式执行下面的命令,而不是用命中断点的方式执行命令(部分命令除外)。
autodsym
lldb命令target symbols add [<symfile>]
需要指定符号文件的地址。我经常需要重签调试各个发布版本,手动输入符号表太麻烦了,所以写了autodsym
命令,执行此命令就可以省略符号文件路径的输入。建议新增一个启动符号的断点(如UIApplicationMain)用于自动执行此命令。
# 以下两个命令效果相同。注意Xcode需要有访问路径的权限,autodsym并不提醒授权
(lldb) autodsym
(lldb) target symbol add /path/to/dSYM
deletefile
开发中常常需要删除沙盒中的某些文件以重置状态,deletefile
命令可以快速删除指定路径的文件夹。有些数据还留在内存中,所以建议删除后立即重新运行项目。这是我最经常调用的命令之一。
# 删除沙盒里所有的文件
(lldb) deletefile -a
# 删除~/Documents文件夹
(lldb) deletefile -d
# 删除~/Library文件夹
(lldb) deletefile -l
# 删除~/tmp文件夹
(lldb) deletefile -t
# 删除~/Library/Caches文件夹
(lldb) deletefile -c
# 删除~Library/Preferences文件夹
(lldb) deletefile -p
# 删除沙盒内指定路径的文件
(lldb) deletefile -f path/to/fileOrDirectory
pbundlepath & phomedirectory
# 打印APP的主bundle的路径
(lldb) pbundlepath
[HMLLDB] /Users/pal/Library/Developer/CoreSimulator/Devices/D90D74C6-DBDF-4976-8BEF-E7BA549F8A89/data/Containers/Bundle/Application/84AE808C-6703-488D-86A2-C90004434D3A/Kingfisher-Demo.app
# 打印APP沙盒的路径
(lldb) phomedirectory
[HMLLDB] /Users/pal/Library/Developer/CoreSimulator/Devices/D90D74C6-DBDF-4976-8BEF-E7BA549F8A89/data/Containers/Data/Application/3F3DF0CD-7B57-4E69-9F15-EB4CCA7C4DD8
# 如果是在模拟器上运行,可以加上-o选项用Finder打开
(lldb) pbundlepath -o
(lldb) phomedirectory -o
fclass & fsubclass & fsuperclass & fmethod
这几个命令针对Swift做了优化,输入Swift类可以省略命名空间。
虽然Xcode自带有相关功能,但有时反应慢,而且Xcode不支持查看二进制库的类。
fclass
查找并打印所有包含指定字符串的类名,不区分大小写。
语法:
fclass <class_name> [-p <protocol>]
(lldb) fclass NormalLoadingViewController
[HMLLDB] Waiting...
[HMLLDB] Count: 1
Kingfisher_Demo.NormalLoadingViewController (0x102148fa8, Kingfisher-Demo)
# 不区分大小写,包含匹配
(lldb) fclass image
[HMLLDB] Waiting...
[HMLLDB] Count: 672
Kingfisher.ImageLoadingProgressSideEffect (0x10124ae18, Kingfisher)
Kingfisher.GIFAnimatedImage (0x10124a3e0, Kingfisher)
...
Kingfisher_Demo.DetailImageViewController (0x1009068e8, Kingfisher-Demo)
Kingfisher_Demo.AVAssetImageGeneratorViewController (0x100904568, Kingfisher-Demo)
...
UIImagePickerController (0x1ea918240, UIKitCore)
_UIStackedImageContainerView (0x1ea90fac8, UIKitCore)
...
# 选项 -p:找出遵守协议的类
# 例子:找出遵守UICollectionViewDelegate协议的类,且名称包含controller
(lldb) fclass controller -p UICollectionViewDelegate
[HMLLDB] Waiting...
[HMLLDB] Count: 16
UIActivityContentViewController (0x224739570, ShareSheet)
UIPrintPreviewViewController (0x225b5f408, PrintKitUI)
UIDebuggingSpecViewController (0x2252cfeb8, UIKitCore)
UIDebuggingInformationHierarchyViewController (0x2252cfc60, UIKitCore)
UICollectionViewController (0x2245d4e50, UIKitCore)
SUIKSearchResultsCollectionViewController (0x2256e82b8, Preferences)
Kingfisher_Demo.InfinityCollectionViewController (0x10079ac18, Kingfisher-Demo)
Kingfisher_Demo.HighResolutionCollectionViewController (0x100799ed0, Kingfisher-Demo)
Kingfisher_Demo.OrientationImagesViewController (0x100799cc8, Kingfisher-Demo)
Kingfisher_Demo.ImageDataProviderCollectionViewController (0x100799bb0, Kingfisher-Demo)
Kingfisher_Demo.IndicatorCollectionViewController (0x100799328, Kingfisher-Demo)
Kingfisher_Demo.ProcessorCollectionViewController (0x100799178, Kingfisher-Demo)
Kingfisher_Demo.NormalLoadingViewController (0x1007989b8, Kingfisher-Demo)
_UISearchSuggestionsListViewController (0x2252dcb40, UIKitCore)
_UIAlertControllerTextFieldViewController (0x2245d13b8, UIKitCore)
UIActivityGroupViewController (0x224739520, ShareSheet)
# 例子:找出所有遵守UICollectionViewDelegate协议的类
(lldb) fclass -p UICollectionViewDelegate
[HMLLDB] Waiting...
[HMLLDB] Count: 30
...
fsubclass
查找并打印一个类的所有子类。
(lldb) fsubclass UICollectionViewController
[HMLLDB] Waiting...
[HMLLDB] Subclass count: 10
Kingfisher_Demo.InfinityCollectionViewController (0x102cf2c18, Kingfisher_Demo)
Kingfisher_Demo.HighResolutionCollectionViewController (0x102cf1ed0, Kingfisher_Demo)
...
fsuperclass
查找并打印一个类的父类。
(lldb) fsuperclass UIButton
[HMLLDB] UIButton : UIControl : UIView : UIResponder : NSObject
(lldb) fsuperclass KingfisherManager
[HMLLDB] Kingfisher.KingfisherManager : Swift._SwiftObject
fmethod
在方法列表中查找并打印方法,常用来查找一个方法在哪些类中被实现。在Swift项目中用处较小。
# 全局查找含clear的方法,不区分大小写
(lldb) fmethod clear
[HMLLDB] Waiting...
[HMLLDB] Methods count: 3725
(-) clearMemoryCache (0x10340c174, Kingfisher)
Type encoding:v16@0:8
Class:Kingfisher.ImageCache
(-) _clearAllSpecifiers (0x1c7f1959c, Preferences)
Type encoding:v16@0:8
Class:PSSpecifierDataSource
(-) _clearCells (0x2160258c0, ScreenReaderOutput)
Type encoding:v16@0:8
Class:SCRO2DBrailleCanvas
...
# 选项-c:打印指定类的方法列表,区分大小写
(lldb) fmethod -c ImageCache
[HMLLDB] Waiting...
[HMLLDB] Class: Kingfisher.ImageCache (0x10348de18, Kingfisher)
Instance methods count: 3. Class method count: 0.
(-) backgroundCleanExpiredDiskCache (0x10340dd6c, Kingfisher)
Type encoding:v16@0:8
(-) cleanExpiredDiskCache (0x10340c84c, Kingfisher)
Type encoding:v16@0:8
(-) clearMemoryCache (0x10340c174, Kingfisher)
Type encoding:v16@0:8
methods & properties & ivars
相当于调用NSObject的私有方法_methodDescription
,_propertyDescription
,_ivarDescription
。上文提到的其他LLDB命令库也有这些命令,但HMLLDB
针对Swift类做了优化,省去了输入命名空间,并优化了报错信息。
# 语法:
methods [--short] <className/classInstance>
properties <className/classInstance>
ivars <Instance>
(lldb) methods NormalLoadingViewController
[HMLLDB] <Kingfisher_Demo.NormalLoadingViewController: 0x10d55ffa8>:
in Kingfisher_Demo.NormalLoadingViewController:
Instance Methods:
- (id) collectionView:(id)arg1 cellForItemAtIndexPath:(id)arg2; (0x10d523f30, Kingfisher-Demo)
- (long) collectionView:(id)arg1 numberOfItemsInSection:(long)arg2; (0x10d522a20, Kingfisher-Demo)
- (void) collectionView:(id)arg1 willDisplayCell:(id)arg2 forItemAtIndexPath:(id)arg3; (0x10d523af0, Kingfisher-Demo)
- (void) collectionView:(id)arg1 didEndDisplayingCell:(id)arg2 forItemAtIndexPath:(id)arg3; (0x10d522cb0, Kingfisher-Demo)
- (id) initWithCoder:(id)arg1; (0x10d522960, Kingfisher-Demo)
...
# 这3个命令只能用于NSObject的子类
(lldb) methods KingfisherManager
[HMLLDB] KingfisherManager is not a subclass of NSObject
ivarsinfo
打印指定类的ivars的信息,我一般用于查看offset。
# 语法:
ivarsinfo <className>
(lldb) ivarsinfo UIView
[HMLLDB] UIView (0x22658d3b8, UIKitCore)
_constraintsExceptingSubviewAutoresizingConstraints
typeEncoding:@"NSMutableArray"
offset:16 hex:0x10
_cachedTraitCollection
typeEncoding:@"UITraitCollection"
offset:24 hex:0x18
_animationInfo
typeEncoding:@"UIViewAnimationInfo"
offset:32 hex:0x20
...
bpframe
设置一个断点,只有匹配指定堆栈/地址时才暂停。
注意:
- 关键词用空格分开
- 按提供的关键词顺序匹配
- 即使命中断点后没有暂停,耗时也是非常高的。建议不要设置高频符号或地址。
# 语法:
bpframe [--one-shot] <symbol or address> <stack keyword 1> <stack keyword 2> ... <stack keyword n>
# 当命中"setupChildViewControllers:"符号,且堆栈包含"otherFunction"关键词时才暂停
(lldb) bpframe setupChildViewControllers: otherFunction
# 当命中"setupChildViewControllers:"符号,且堆栈包含"function_1"和"function_2"关键词时才暂停
(lldb) bpframe setupChildViewControllers: function_1 function_2
# 当命中"0x1025df6c0"地址,且堆栈包含"0x19261c1c0"和"0x19261bec0"地址时才暂停
(lldb) bpframe 0x1025df6c0 0x19261c1c0 0x19261bec0
# 当命中"0x1025df6c0"地址,且堆栈包含"otherFunction"关键词和"0x19261bec0"地址时才暂停
(lldb) bpframe 0x1025df6c0 otherFunction 0x19261bec0
# --one-shot/-o; 当触发第一次暂停后,自动删除断点
(lldb) bpframe -o setupChildViewControllers: otherFunction
(lldb) bpframe -o 0x1025df6c0 otherFunction
bpmessage
给指定类的指定方法设置一个断点,即使这个方法是其父类的实现也有效。
语法:
bpmessage -[<class_name> <selector>]
bpmessage +[<class_name> <selector>]
例子:
(lldb) bpmessage -[MyModel release]
(lldb) bpmessage -[MyModel dealloc]
chisel
里的bmessage
也有相同的功能,它是通过条件断点实现的。也就是说兄弟类或父类对象也会命中断点,只是没有暂停程序,这会非常耗时。
HMLLDB
的bpmessage
是通过runtime实现的,当指定类的方法是其父类实现时,会给指定类添加一个方法和实现,这个实现里直接再调用父类的实现,所以非常高效。上述的例子,给指定类的release或dealloc方法添加断点,就是最佳实践。
bpmethod
设置一个断点,当前线程执行到下一个OC方法的实现时暂停(必须是通过objc_msgSend
调用的方法)。
当调试汇编代码时,遇到objc_msgSend
会非常麻烦,常常需要查看对应方法的实现,而想要跳到对应方法的实现,操作有点繁琐,这个命令解决了这个痛点。
# 我想进入(step into)到对应方法的实现,而不是进入(step into)objc_msgSend的实现
0x10075a574 <+64>: bl 0x10075a95c ; symbol stub for: objc_msgSend
# 解决方案
(lldb) bpmethod
[HMLLDB] Target thread index:1, thread id:1692289.
[HMLLDB] Done! You can continue program execution.
# --continue/-c; 当执行此命令后,程序自动继续执行
Continue program execution after executing bpmethod
(lldb) bpmethod -c
cbt
cbt
命令:基于fp/lr寄存器,完整展示当前线程调用栈。
Xcode的"Debug Navigator"和bt
命令:基于DWARF信息,展示线程的调用栈。
某些情况下,基于DWARF的回溯可能会丢失调用帧,而arm64架构应用实际执行过程是依据fp/lr寄存器的,基于这个原因,开发了cbt
命令。
注意:
- 此命令只支持arm64架构设备
- 如果编辑时加了
-fomit-frame-pointer
参数,cbt
命令也不能找到隐藏的frame。因此,建议cbt
和bt
命令一起使用
rr
lldb自带register read
命令的别名,新增选项打印栈寄存器sp指向地址的值。
# 系统命令"register read"的别名
(lldb) rr
# 相当于"register read -a"
(lldb) rr -a
# 打印寄存器的同时,同时打印[sp, (sp + offset)]地址的值
(lldb) rr -s 64
General Purpose Registers:
x0 = 0x000000016dc24e48
x1 = 0x0000000000000000
x2 = 0x0000000129d0fd70
x3 = 0x0000000281a30000
x4 = 0x0000000281a30000
x5 = 0x0000000281a30000
x6 = 0x0000000000000000
x7 = 0x000000016dc24aae
x8 = 0x0000000000000006
x9 = 0x0000000000000002
x10 = 0x000000013a0efd77
x11 = 0x01ff00012d00b800
x12 = 0x0000000000000042
x13 = 0x000000012d00bc10
x14 = 0x00000001ba0ec000
x15 = 0x0000000213521b88 (void *)0x0000000213521b60: UIButton
x16 = 0x00000001d31fa170 libobjc.A.dylib`objc_release
x17 = 0x00000002162b2f90 (void *)0x00000001d31fa170: objc_release
x18 = 0x0000000000000000
x19 = 0x0000000281a30000
x20 = 0x0000000129d0fd70
x21 = 0x00000001021de9d0 "clickBtn:"
x22 = 0x0000000129d0a7a0
x23 = 0x00000001021de9d0 "clickBtn:"
x24 = 0x0000000213536800 UIKitCore`UIApp
x25 = 0x0000000000000000
x26 = 0x00000002047c18ff
x27 = 0x0000000281a30000
x28 = 0x0000000000000001
fp = 0x000000016dc24e60
lr = 0x00000001021de170 Demo`-[ViewController clickBtn:] + 52 at ViewController.m:29
sp = 0x000000016dc24e30
pc = 0x00000001021de178 Demo`-[ViewController clickBtn:] + 60 at ViewController.m:31:1
cpsr = 0x80001000
0x16dc24e30: 0x0000000281a30000
0x16dc24e38: 0x000000016dc24e48
0x16dc24e40: 0x0000000000000000
0x16dc24e48: 0x0000000129d0fd70
0x16dc24e50: 0x00000001021de9d0 "clickBtn:"
0x16dc24e58: 0x0000000129d0a7a0
0x16dc24e60: 0x000000016dc24e90
0x16dc24e68: 0x00000001bcd84f1c UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 100
0x16dc24e70: 0x0000000281a30000
(lldb)rr x0 sp -s 0x10
[HMLLDB] register read x0 sp
x0 = 0x0000000000000000
sp = 0x000000016fb2cdf0
0x16fb2cdf0: 0x000000010110b8b0
0x16fb2cdf8: 0x00000001002e5008 "clickBtn:"
0x16fb2ce00: 0x0000000101137b80
twos_complement_to_int
把补码转换为有符号数。
语法:
twos_complement_to_int <twos_complement_value> <bit_width>
例子:
(lldb) twos_complement_to_int 0xfffffffffffffff0 64
[HMLLDB] -16, -0x10
reference
扫描machO获取指定地址所有的引用地址。
此命令和Hopper Disassembler软件的References to功能非常相似,不同之处是:
- reference比Hopper Disassembler的速度快非常多。
- reference支持运行时扫描,也就是支持扫描__DATA Segment里完成初始化后的数据,这意味着你可以查询image范围外的地址。支持扫描的地址比Hopper Disassembler更多。
- 少数情况下,reference不如Hopper Disassembler的扫描结果完整。
语法:
reference <address> <image_name>
# 例子 A:调试过程看到了如下内容,想查询UIKitCore里的"objc_msgSend$sendAction:toTarget:fromSender:forEvent:"函数引用。
(lldb) dis -n "-[UIControl sendAction:to:forEvent:]"
UIKitCore`-[UIControl sendAction:to:forEvent:]:
...
0x19a7eb730 <+108>: bl 0x19bd627a0 ; objc_msgSend$sendAction:toTarget:fromSender:forEvent:
...
# 查询所有调用"objc_msgSend$sendAction:toTarget:fromSender:forEvent:"函数的地址:0x19bd627a0
(lldb) reference 0x19bd627a0 UIKitCore
[HMLLDB] These are the scan results:
0x19a7eb730: UIKitCore`-[UIControl sendAction:to:forEvent:] + 108
0x19ac25624: UIKitCore`-[UITabBar _sendAction:withEvent:] + 388
0x19ac2ed14: UIKitCore`-[UIToolbar _sendAction:withEvent:] + 328
0x19b2fe8f0: UIKitCore`-[UIApplication _performKeyCommandInvocation:allowsRepeat:] + 280
0x19b437250: UIKitCore`-[UITableView _updateCell:withValue:] + 224
[HMLLDB] Scan result count:5
[HMLLDB] Scan result count in memory:0
# 例子 B:查询DemoApp里所有用到UIPasteboard类的地址,注意这个地址是在这个image(DemoApp)范围外的。
# 1.找到UIPasteboard类对象的加载地址
(lldb) image lookup -vs UIPasteboard
...
Symbol: id = {0x00036667}, range = [0x00000001eef79138-0x00000001eef79160), name="UIPasteboard", mangled="OBJC_CLASS_$_UIPasteboard"
# 更建议使用"fclass"命令查找
(lldb) fclass UIPasteboard
...
UIPasteboard (0x1eef79138, UIKitCore)
...
# 2.在DemoApp中查找UIPasteboard类对象的引用地址
(lldb) reference 0x1eef79138 DemoApp
[HMLLDB] Scan result count:0
[HMLLDB] These are the scan results in memory:
0x100a2ae9c: DemoApp`-[ViewController viewDidLoad] + 68 at ViewController.mm:27:6
[HMLLDB] Scan result count in memory:1
# 例子 C:查找DemoApp里所有用到setenv函数的地址。注意这个地址是在这个image(DemoApp)范围外的。
# 1.找到setenv函数的加载地址
(lldb) dis -n setenv
libsystem_c.dylib`setenv:
0x19fa6c6d0 <+0>: pacibsp
0x19fa6c6d4 <+4>: stp x22, x21, [sp, #-0x30]!
...
# 2.在DemoApp里查找setenv函数的所有引用
(lldb) reference 0x19fa6c6d0 DemoApp
[HMLLDB] Scan result count:0
[HMLLDB] These are the scan results in memory:
0x104a50768: DemoApp`symbol stub for: setenv + 4
[HMLLDB] Scan result count in memory:1
# 3.上一步发现只被stub函数引用,需要进一步找哪些地方用了桩函数。
# 注意上一步找到的偏移+4,桩函数的地址:0x104a50764 = 0x104a50768 - 4
(lldb) reference 0x104a50764 DemoApp
[HMLLDB] These are the scan results:
0x104a470ec: DemoApp`-[ViewController viewDidLoad] + 660 at ViewController.mm:46:5
0x104a47388: DemoApp`-[ViewController clickBtn1:] + 36 at ViewController.mm:72:5
[HMLLDB] Scan result count:2
[HMLLDB] Scan result count in memory:0
注意:
- 这个命令扫描大型machO的时候非常耗时。我实测扫描UIKitCore需要40秒,扫描公司的一个App需要6分钟。(但也比Hopper快多了,Hopper扫描我公司的App超1小时,得付费使用...)
- 这个命令会消耗大量内存,使用之前清理释放内存能加快扫描速度。
- 这个命令扫描所有的b/bl指令,且能分析绝大多数的adr/adrp指令及其后续指令。
- 在使用的时候需要考虑"stub"函数和"island"函数。
adrp
快速获取adrp指令的计算结果。
0x189aef040 <+32>: adrp x8, 348413
,当遇到这行汇编指令,需要自己去计算得到x8寄存器的值太麻烦了,adrp
命令能帮助我快速获取结果。
语法:
adrp <pc address>
adrp <pc address> <immediate>
adrp <pc address> <adrp> <register> <immediate>
adrp <pc address> <+offset> <adrp> <register> <immediate>
例子:
(lldb) adrp 0x189aef040
[HMLLDB] x8: 0x1debec000, 8032010240
(lldb) adrp 0x189aef040 348413
[HMLLDB] result: 0x1debec000, 8032010240
(lldb) adrp 0x189aef040: adrp x8, 348413
[HMLLDB] x8: 0x1debec000, 8032010240
(lldb) adrp 0x189aef040 <+32>: adrp x8, 348413
[HMLLDB] x8: 0x1debec000, 8032010240
edisassemble
系统自带命令disassemble
增强版本,新增了如下注解:
- 计算adr/adrp指令的值。
- 找到部分branch指令的真实目标。
语法:和系统命令disassemble一模一样
例子:
(lldb) edisassemble -s 0x107ad4504
(lldb) edis -a 0x107ad4504
(lldb) edis -n "-[UIDevice systemVersion]"
# 如下例子是系统命令disassemble和增强版本edisassemble的对比
(lldb) dis -n "-[UIDevice systemVersion]"
UIKitCore`-[UIDevice systemVersion]:
0x1afbe9e34 <+0>: pacibsp
0x1afbe9e38 <+4>: stp x20, x19, [sp, #-0x20]!
0x1afbe9e3c <+8>: stp x29, x30, [sp, #0x10]
0x1afbe9e40 <+12>: add x29, sp, #0x10
0x1afbe9e44 <+16>: adrp x2, 329379
0x1afbe9e48 <+20>: add x2, x2, #0xef0 ; @"ProductVersion"
0x1afbe9e4c <+24>: bl 0x1b0b06000 ; objc_msgSend$_deviceInfoForKey:
0x1afbe9e50 <+28>: bl 0x1b2be97f0
0x1afbe9e54 <+32>: mov x19, x0
0x1afbe9e58 <+36>: adrp x8, 329342
0x1afbe9e5c <+40>: add x8, x8, #0x90 ; @"Unknown"
0x1afbe9e60 <+44>: cmp x0, #0x0
0x1afbe9e64 <+48>: csel x0, x8, x0, eq
0x1afbe9e68 <+52>: bl 0x1b2be9ad0
0x1afbe9e6c <+56>: mov x20, x0
0x1afbe9e70 <+60>: bl 0x1b2be99c0
0x1afbe9e74 <+64>: mov x0, x20
0x1afbe9e78 <+68>: ldp x29, x30, [sp, #0x10]
0x1afbe9e7c <+72>: ldp x20, x19, [sp], #0x20
0x1afbe9e80 <+76>: retab
(lldb) edis -n "-[UIDevice systemVersion]"
UIKitCore`-[UIDevice systemVersion]:
0x1afbe9e34 <+0>: pacibsp
0x1afbe9e38 <+4>: stp x20, x19, [sp, #-0x20]!
0x1afbe9e3c <+8>: stp x29, x30, [sp, #0x10]
0x1afbe9e40 <+12>: add x29, sp, #0x10
0x1afbe9e44 <+16>: adrp x2, 329379 ; x2 = 0x20028c000
0x1afbe9e48 <+20>: add x2, x2, #0xef0 ; @"ProductVersion"
0x1afbe9e4c <+24>: bl 0x1b0b06000 ; objc_msgSend$_deviceInfoForKey:
0x1afbe9e50 <+28>: bl 0x1b2be97f0 ; br x16, x16 = 0x1a538ade4 libobjc.A.dylib`objc_claimAutoreleasedReturnValue
0x1afbe9e54 <+32>: mov x19, x0
0x1afbe9e58 <+36>: adrp x8, 329342 ; x8 = 0x200267000
0x1afbe9e5c <+40>: add x8, x8, #0x90 ; @"Unknown"
0x1afbe9e60 <+44>: cmp x0, #0x0
0x1afbe9e64 <+48>: csel x0, x8, x0, eq
0x1afbe9e68 <+52>: bl 0x1b2be9ad0 ; br x16, x16 = 0x1a537e148 libobjc.A.dylib`objc_retainAutoreleaseReturnValue
0x1afbe9e6c <+56>: mov x20, x0
0x1afbe9e70 <+60>: bl 0x1b2be99c0 ; br x16, x16 = 0x1a537fc28 libobjc.A.dylib`objc_release_x19
0x1afbe9e74 <+64>: mov x0, x20
0x1afbe9e78 <+68>: ldp x29, x30, [sp, #0x10]
0x1afbe9e7c <+72>: ldp x20, x19, [sp], #0x20
0x1afbe9e80 <+76>: retab
tracefunction
基于指令,一步步追踪函数跳转,直到命中下个断点停止。
如果你设置了如下两个断点:
当命中了第一个断点后,执行
tracefunction
命令结果如下:
(lldb) tracefunction
[HMLLDB] ==========Begin========================================================
Demo`-[ViewController buttonAction] + 24 at ViewController.m:28:24 (0x100c33314)
Demo`-[ViewController buttonAction] + 24 at ViewController.m:28:24 (0x100c33314)
Demo`symbol stub for: objc_alloc_init + 8 (0x100c3e7dc)
libobjc.A.dylib`objc_alloc_init + 32 (0x1a7114f3c)
libobjc.A.dylib`_objc_rootAllocWithZone + 36 (0x1a711140c)
libobjc.A.dylib`symbol stub for: calloc + 12 (0x1a713a524)
libsystem_malloc.dylib`calloc + 20 (0x1a06a7b78)
libsystem_malloc.dylib`_malloc_zone_calloc + 84 (0x1a06aae58)
libsystem_malloc.dylib`default_zone_calloc + 32 (0x1a06a79d4)
libsystem_malloc.dylib`nanov2_calloc + 156 (0x1a06bc74c)
libsystem_malloc.dylib`nanov2_allocate + 124 (0x1a06bc160)
libsystem_malloc.dylib`nanov2_allocate + 340 (0x1a06bc238)
libsystem_malloc.dylib`symbol stub for: _platform_memset + 8 (0x1a06c3448)
libsystem_platform.dylib`_platform_memset + 208 (0x1ff580e50)
libsystem_malloc.dylib`nanov2_allocate + 460 (0x1a06bc2b0)
libsystem_malloc.dylib`nanov2_calloc + 172 (0x1a06bc75c)
libsystem_malloc.dylib`_malloc_zone_calloc + 132 (0x1a06aae88)
libobjc.A.dylib`_objc_rootAllocWithZone + 100 (0x1a711144c)
libobjc.A.dylib`objc_alloc_init + 64 (0x1a7114f5c)
libobjc.A.dylib`objc_msgSend + 76 (0x1a710df6c)
libobjc.A.dylib`-[NSObject init] (0x1a711d184)
Demo`-[ViewController buttonAction] + 48 at ViewController.m:29:1 (0x100c3332c)
[HMLLDB] ==========End========================================================
[HMLLDB] Instruction count: 295
[HMLLDB] Function count: 22
[HMLLDB] Start time: 22:57:10
[HMLLDB] Stop time: 22:57:11
Process 18247 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
frame #0: 0x0000000100c3332c Demo`-[ViewController buttonAction](self=0x000000010120c9e0, _cmd="buttonAction") at ViewController.m:29:1
26
27 - (void)buttonAction {
28 NSObject *object = [[NSObject alloc] init];
-> 29 }
^
30
31
32
Target 0: (Demo) stopped.
# 最多打印500个函数,达到目标数量后停止执行
(lldb) tracefunction -m 500
...
可以看到打印了这个过程里所有执行到的函数,堆栈里看不到的调用过程都能一览无遗。我常在两种情况下使用这个命令,一是查看某行代码是否会调用到某个函数,二是查看某个过程(两个断点之间)是否有函数被hook了。
还有一个不常见的目的,因为有输出总共执行的指令数量(Instruction count: 295),所以可以用来判断某个过程(两个断点之间)的性能耗时。(并非准确,还要考虑多发射和数据冒险等因素)
注意:这个命令是指令级别的,所以非常非常非常耗时。只追踪一行[[UIView alloc] init]
就能耗时90秒。
traceinstruction
追踪指令,直到命中下个断点停止。
如果你设置了如下两个断点:
当命中了第一个断点后,执行
traceinstruction
命令结果如下:
(lldb) traceinstruction
[HMLLDB] ==========Begin========================================================
Demo`-[ViewController buttonAction] + 24 at ViewController.m:28:24 ldr x0, [x8, #0xc58] (0x104b23314)
Demo`symbol stub for: objc_alloc_init nop (0x104b2e7d4)
Demo`symbol stub for: objc_alloc_init + 4 ldr x16, #0x5960 ; (void *)0x00000001a7114f1c: objc_alloc_init (0x104b2e7d8)
Demo`symbol stub for: objc_alloc_init + 8 br x16 (0x104b2e7dc)
libobjc.A.dylib`objc_alloc_init pacibsp (0x1a7114f1c)
libobjc.A.dylib`objc_alloc_init + 4 stp x29, x30, [sp, #-0x10]! (0x1a7114f20)
libobjc.A.dylib`objc_alloc_init + 8 mov x29, sp (0x1a7114f24)
libobjc.A.dylib`objc_alloc_init + 12 cbz x0, 0x1a7114f40 ; <+36> (0x1a7114f28)
libobjc.A.dylib`objc_alloc_init + 16 ldr x8, [x0] (0x1a7114f2c)
libobjc.A.dylib`objc_alloc_init + 20 and x8, x8, #0xffffffff8 (0x1a7114f30)
libobjc.A.dylib`objc_alloc_init + 24 ldrb w8, [x8, #0x1d] (0x1a7114f34)
libobjc.A.dylib`objc_alloc_init + 28 tbz w8, #0x6, 0x1a7114f60 ; <+68> (0x1a7114f38)
libobjc.A.dylib`objc_alloc_init + 32 bl 0x1a71113e8 ; _objc_rootAllocWithZone (0x1a7114f3c)
libobjc.A.dylib`_objc_rootAllocWithZone pacibsp (0x1a71113e8)
libobjc.A.dylib`_objc_rootAllocWithZone + 4 stp x20, x19, [sp, #-0x20]! (0x1a71113ec)
libobjc.A.dylib`_objc_rootAllocWithZone + 8 stp x29, x30, [sp, #0x10] (0x1a71113f0)
libobjc.A.dylib`_objc_rootAllocWithZone + 12 add x29, sp, #0x10 (0x1a71113f4)
libobjc.A.dylib`_objc_rootAllocWithZone + 16 mov x19, x0 (0x1a71113f8)
libobjc.A.dylib`_objc_rootAllocWithZone + 20 ldrh w20, [x0, #0x1c] (0x1a71113fc)
libobjc.A.dylib`_objc_rootAllocWithZone + 24 and x1, x20, #0x1ff0 (0x1a7111400)
libobjc.A.dylib`_objc_rootAllocWithZone + 28 cbz w1, 0x1a7111450 ; <+104> (0x1a7111404)
libobjc.A.dylib`_objc_rootAllocWithZone + 32 mov w0, #0x1 (0x1a7111408)
libobjc.A.dylib`_objc_rootAllocWithZone + 36 bl 0x1a713a518 ; symbol stub for: calloc (0x1a711140c)
libobjc.A.dylib`symbol stub for: calloc adrp x17, 274572 (0x1a713a518)
libobjc.A.dylib`symbol stub for: calloc + 4 add x17, x17, #0xea0 (0x1a713a51c)
libobjc.A.dylib`symbol stub for: calloc + 8 ldr x16, [x17] (0x1a713a520)
libobjc.A.dylib`symbol stub for: calloc + 12 braa x16, x17 (0x1a713a524)
libsystem_malloc.dylib`calloc mov x2, x1 (0x1a06a7b64)
libsystem_malloc.dylib`calloc + 4 mov x1, x0 (0x1a06a7b68)
libsystem_malloc.dylib`calloc + 8 adrp x0, 292189 (0x1a06a7b6c)
libsystem_malloc.dylib`calloc + 12 add x0, x0, #0x0 (0x1a06a7b70)
libsystem_malloc.dylib`calloc + 16 mov w3, #0x1 (0x1a06a7b74)
libsystem_malloc.dylib`calloc + 20 b 0x1a06aae04 ; _malloc_zone_calloc (0x1a06a7b78)
libsystem_malloc.dylib`_malloc_zone_calloc pacibsp (0x1a06aae04)
libsystem_malloc.dylib`_malloc_zone_calloc + 4 stp x24, x23, [sp, #-0x40]! (0x1a06aae08)
...
...
...
libobjc.A.dylib`objc_alloc_init + 36 adrp x8, 202134 (0x1a7114f40)
libobjc.A.dylib`objc_alloc_init + 40 add x1, x8, #0x4da (0x1a7114f44)
libobjc.A.dylib`objc_alloc_init + 44 ldp x29, x30, [sp], #0x10 (0x1a7114f48)
libobjc.A.dylib`objc_alloc_init + 48 autibsp (0x1a7114f4c)
libobjc.A.dylib`objc_alloc_init + 52 eor x16, x30, x30, lsl #1 (0x1a7114f50)
libobjc.A.dylib`objc_alloc_init + 56 tbz x16, #0x3e, 0x1a7114f5c ; <+64> (0x1a7114f54)
libobjc.A.dylib`objc_alloc_init + 64 b 0x1a710df20 ; objc_msgSend (0x1a7114f5c)
libobjc.A.dylib`objc_msgSend cmp x0, #0x0 (0x1a710df20)
libobjc.A.dylib`objc_msgSend + 4 b.le 0x1a710dff0 ; <+208> (0x1a710df24)
libobjc.A.dylib`objc_msgSend + 8 ldr x13, [x0] (0x1a710df28)
libobjc.A.dylib`objc_msgSend + 12 and x16, x13, #0x7ffffffffffff8 (0x1a710df2c)
libobjc.A.dylib`objc_msgSend + 16 mov x10, x0 (0x1a710df30)
libobjc.A.dylib`objc_msgSend + 20 movk x10, #0x6ae1, lsl #48 (0x1a710df34)
libobjc.A.dylib`objc_msgSend + 24 autda x16, x10 (0x1a710df38)
libobjc.A.dylib`objc_msgSend + 28 mov x15, x16 (0x1a710df3c)
libobjc.A.dylib`objc_msgSend + 32 ldr x11, [x16, #0x10] (0x1a710df40)
libobjc.A.dylib`objc_msgSend + 36 tbnz w11, #0x0, 0x1a710dfa0 ; <+128> (0x1a710df44)
libobjc.A.dylib`objc_msgSend + 40 and x10, x11, #0xffffffffffff (0x1a710df48)
libobjc.A.dylib`objc_msgSend + 44 eor x12, x1, x1, lsr #7 (0x1a710df4c)
libobjc.A.dylib`objc_msgSend + 48 and x12, x12, x11, lsr #48 (0x1a710df50)
libobjc.A.dylib`objc_msgSend + 52 add x13, x10, x12, lsl #4 (0x1a710df54)
libobjc.A.dylib`objc_msgSend + 56 ldp x17, x9, [x13], #-0x10 (0x1a710df58)
libobjc.A.dylib`objc_msgSend + 60 cmp x9, x1 (0x1a710df5c)
libobjc.A.dylib`objc_msgSend + 64 b.ne 0x1a710df70 ; <+80> (0x1a710df60)
libobjc.A.dylib`objc_msgSend + 68 eor x10, x10, x1 (0x1a710df64)
libobjc.A.dylib`objc_msgSend + 72 eor x10, x10, x16 (0x1a710df68)
libobjc.A.dylib`objc_msgSend + 76 brab x17, x10 (0x1a710df6c)
libobjc.A.dylib`-[NSObject init] ret (0x1a711d184)
Demo`-[ViewController buttonAction] + 32 at ViewController.m:28:24 mov x8, x0 (0x104b2331c)
Demo`-[ViewController buttonAction] + 36 at ViewController.m:28:24 add x0, sp, #0x8 (0x104b23320)
Demo`-[ViewController buttonAction] + 40 at ViewController.m:28:15 str x8, [sp, #0x8] (0x104b23324)
Demo`-[ViewController buttonAction] + 44 at ViewController.m:28:15 mov x1, #0x0 (0x104b23328)
Demo`-[ViewController buttonAction] + 48 at ViewController.m:29:1 bl 0x104b2e8c4 ; symbol stub for: objc_storeStrong (0x104b2332c)
[HMLLDB] ==========End========================================================
[HMLLDB] Instruction count: 295
[HMLLDB] Start time: 22:59:34
[HMLLDB] Stop time: 22:59:36
Process 18265 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
frame #0: 0x0000000104b2332c Demo`-[ViewController buttonAction](self=0x0000000153d0b5f0, _cmd="buttonAction") at ViewController.m:29:1
26
27 - (void)buttonAction {
28 NSObject *object = [[NSObject alloc] init];
-> 29 }
^
30
31
32
Target 0: (Demo) stopped.
# 最多打印8000行指令,达到目标数量后停止执行
(lldb) traceinstruction -m 8000
...
由于篇幅太长,中间省略了很多行指令。我一般用于查看同一个函数内部的汇编代码是如何执行的。
类似于tracefunction
指令,这个指令也非常耗时。
trace-step-over-instruction
lldb自带命令thread step-inst-over --count 100
存在两个问题:
- 只能看到最终结果,看不到这100条指令的过程。我需要在每次"step over instruction"后看到当前pc寄存器的地址。
- 当遇到特定的指令如"bl","ret",表现不符合预期。
trace-step-over-instruction
解决了这些问题。
语法:
trace-step-over-instruction <count>
例子:
(lldb) trace-step-over-instruction 20
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] pacibsp (0x19b1d79d0)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 4 stp x22, x21, [sp, #-0x30]! (0x19b1d79d4)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 8 stp x20, x19, [sp, #0x10] (0x19b1d79d8)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 12 stp x29, x30, [sp, #0x20] (0x19b1d79dc)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 16 add x29, sp, #0x20 (0x19b1d79e0)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 20 mov x19, x5 (0x19b1d79e4)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 24 mov x21, x4 (0x19b1d79e8)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 28 mov x20, x3 (0x19b1d79ec)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 32 mov x22, x2 (0x19b1d79f0)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 36 cbnz x3, 0x19b1d7a10 ; <+64> (0x19b1d79f4)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 64 mov x0, #0x2 (0x19b1d7a10)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 68 movk x0, #0x10, lsl #48 (0x19b1d7a14)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 72 bl 0x19e8d6820 (0x19b1d7a18)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 76 cbz w0, 0x19b1d7a38 ; <+104> (0x19b1d7a1c)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 80 mov x0, x20 (0x19b1d7a20)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 84 mov x1, x22 (0x19b1d7a24)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 88 mov x2, x21 (0x19b1d7a28)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 92 mov x3, x19 (0x19b1d7a2c)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 96 bl 0x19e8d6fc0 (0x19b1d7a30)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 100 b 0x19b1d7a54 ; <+132> (0x19b1d7a34)
UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 132 cmp x20, #0x0 (0x19b1d7a54)
pfont
打印设备支持的所有字体名称。
(lldb) pfont
[HMLLDB] Family names count: 81, font names count: 274
familyNames: Academy Engraved LET
fontName: AcademyEngravedLetPlain
familyNames: Al Nile
fontName: AlNile
fontName: AlNile-Bold
familyNames: American Typewriter
fontName: AmericanTypewriter
...
plifecycle
用于打印UIViewController的生命周期。无侵入的方式,加上Xcode可以设置Console字体颜色使输出一目了然,成为了我最喜爱的命令之一。
使用方法:
- 新增一个符号断点Symbolic Breakpoint,在Symbol一行添加需要打印的方法,如
-[UIViewController viewDidAppear:]
- 添加一个Action(Debugger Command),在里面新增
plifecycle
命令 - 勾上这个选项:Automatically continue after evaluating actions
具体配置如下图,我一般会用加上-i选项用来忽略一些系统UIViewController。
我个人常常启用viewDidAppear:
和dealloc
用来以辅助开发,其余默认设置为Disable,按需启动,如下图:
实际输出打印例子如下图,区别于项目本身的输出(Target Output),如果选择了Debugger output,则输出还能集中显示,去除干扰。
但是这个命令还有待优化,一个是会导致页面切换卡顿,所以我一般只启用viewDidAppear:
和dealloc
打印。另一个就是刚启动APP时有可能会触发下面的警告而暂停程序,如果经常出现阻碍了开发,建议按需启动。
# 遇上这个警告,要点击Xcode里的Continue program execution让程序继续跑
Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion.
另外,源码里还另外提供了其它方式去使用LLDB打印生命周期,有兴趣可以看看。
redirect
stdout/stderr重定向。
# 如果用模拟器,可以把Xcode的输出重定向到终端Terminal
# 打开终端,输入"tty"命令,就可以知道其路径了:/dev/ttys000
(lldb) redirect both /dev/ttys000
[HMLLDB] redirect stdout successful
[HMLLDB] redirect stderr successful
push
找到一个UINavigationController,然后push一个指定的UIViewController。
此命令的使用有限制,push MyViewController
相当于要先执行[[MyViewController alloc] init]
,也就是说目标控制器的初始化构造器(Initializer)必须是无依赖的。若一个类初始化构造时需要参数或初始化后还需要传入参数,此命令可能会导致错误的显示甚至程序奔溃。
此处贴个gif演示,没有用Kingfisher演示是因为其demo中的UIViewController依赖于storyboard,不符合使用要求。
showhud & showfps
showhud
命令会在keyWindow上展示一个debug视图,显示内存占用,CPU使用率,主线程FPS。
点击debug视图会present一个新的view controller(如下图),里面的功能后文会有介绍。
showfps
命令已被showhud
替代,项目里保留下来这个极简的工具仅供学习参考。
sandbox
present一个沙盒浏览器,可以查看主bundle目录和数据目录,具有系统分享(如隔空投送)、删除文件等功能。
这里用gif演示(首次调用命令要等待几秒,为了减少gif大小,演示的是初始化后的调用)
这是拉起系统分享的一个例子:
inspect
查看当前页面的UIView对象。还能看到常见的类如UILabel的关键属性值。
request
自动打印http/https请求(WKWebView除外)。
可能会多次打印同一个请求,请自行判断。
environment
诊断当前的环境。可以看到其中一项是[Git commit hash]
,这也是建议clone仓库的原因之一。
(lldb) environment
[HMLLDB] [Python version] 3.8.2 (default, Nov 4 2020, 21:23:28)
[Clang 12.0.0 (clang-1200.0.32.28)]
[HMLLDB] [LLDB version] lldb-1200.0.44.2
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
[HMLLDB] [Host program path] /Applications/Xcode.app/Contents/SharedFrameworks/LLDBRPC.framework/Resources/lldb-rpc-server
[HMLLDB] [Target triple] x86_64h-apple-ios-simulator
[HMLLDB] [Git commit hash] 088f654cb158ffb16019b2deca5dce36256837ad
[HMLLDB] [Optimized] False: 28 True: 0
[HMLLDB] [Xcode version] 1230
[HMLLDB] [Xcode build version] 12C33
[HMLLDB] [Model identifier] x86_64
[HMLLDB] [System version] iOS 13.0
如果发生错误
通过LLDB进行JIT即时编译并不稳定,命令报错是常见的。如果出现问题,请按以下步骤依次检查。
- pull最新代码,核对Xcode版本,
HMLLDB
一般只适配最新的Xcode版本。 - 打开
~/.lldbinit
文件,确保是在末尾导入的HMLLDB.py
文件令其命令不被覆盖。 - 启动APP后,主动点击
Pause program execution
进入LLDB调试模式执行命令,而不是用命中断点的方式执行命令。(除了部分命令) - 重启Xcode。这一般能解决绝大多数问题。
- 重启电脑。有些命令出错会把电脑的LLDB服务玩坏。
- 完成以上步骤命令依然出错。请拷贝错误内容发布到Issue,并执行
environment
命令,其输出也需要发布到Issue。
在我个人来看,编写LLDB复杂命令门槛高的原因主要是即时编译不稳定,可能出错原因都找不到。对比一下,SB API
简直太友好了,还可以看源码帮助理解,另外我还编写了一个命令帮助理解常用SB API
,读者有兴趣可以去看看源码。
存在的问题
HMLLDB
和前言提到的两个项目一样,都有很多问题:
- 如上所说,命令不稳定,实测还发现和设备型号相关。
- 数据常驻内存。没有ARC机制帮忙插入release等代码,设置了
options.SetSuppressPersistentResult(True)
也没能解决问题。 - 没有使用exe_ctx参数,多线程环境中极可能出错。虽然这些命令暂时都没有涉及多线程,但其他情况(如系统内部断点)触发的就不一定了。
- 命令覆盖。LLDB其实有一个函数
UserCommandExists
支持判断已经导入的命令,可以避免自定义命令覆盖。但SB API
居然没有暴露出这个方法。(Xcode 15.0开始开放了这个接口,但好像没必要,大家用我这个库就好了) - 即时编译速度慢,越大型项目执行速度越慢。虽然有在Python中去hook OC的方法,按需执行相关代码,但还是不够快。如果调试工具使用频率很高,还是直接集成到项目中的传统方法效率更高。
- 还没找到加载某些系统framework的方法。
- 仅支持iOS平台,部分命令只支持amr64架构。
未来的计划
毕竟是在Debug模式下的调试命令,上述问题并非急迫的问题,所以未来主要还是开发新的命令。
现有的命令大多是结合我个人经历而开发的。LLDB有足够高的权限,开发新功能除了个人需求还取决于想象力,各位读者如果有什么好的建议和想法,欢迎留言提出来。