LLDB 命令库 HMLLDB 介绍

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])

安装

项目源码:https://github.com/chenhuimao/HMLLDB

  1. 下载源码,强烈建议clone仓库,方便pull代码保持最新的版本。一些命令很可能要随着Xcode版本的更新而调整,我都会及时适配。
  2. 打开(或创建)~/.lldbinit文件(".lldbinit"是完整的文件名,没有多余后缀,放在当前的用户目录下,即~),在末尾新增这一行命令:
command script import <path>

其中<path>是项目里HMLLDB.py这个文件的绝对路径,比如我电脑这一行的是:
command script import /Users/pal/Desktop/gitProjects/HMLLDB/commands/HMLLDB.py

  1. 重启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也有相同的功能,它是通过条件断点实现的。也就是说兄弟类或父类对象也会命中断点,只是没有暂停程序,这会非常耗时。
HMLLDBbpmessage是通过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。因此,建议cbtbt命令一起使用

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功能非常相似,不同之处是:

  • referenceHopper 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.jpg

当命中了第一个断点后,执行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

追踪指令,直到命中下个断点停止。
如果你设置了如下两个断点:

tracefunction.jpg

当命中了第一个断点后,执行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字体颜色使输出一目了然,成为了我最喜爱的命令之一。
使用方法:

  1. 新增一个符号断点Symbolic Breakpoint,在Symbol一行添加需要打印的方法,如-[UIViewController viewDidAppear:]
  2. 添加一个Action(Debugger Command),在里面新增plifecycle命令
  3. 勾上这个选项:Automatically continue after evaluating actions

具体配置如下图,我一般会用加上-i选项用来忽略一些系统UIViewController。


img1.jpg

我个人常常启用viewDidAppear:dealloc用来以辅助开发,其余默认设置为Disable,按需启动,如下图:

img2.jpg

实际输出打印例子如下图,区别于项目本身的输出(Target Output),如果选择了Debugger output,则输出还能集中显示,去除干扰。

img3.jpg

但是这个命令还有待优化,一个是会导致页面切换卡顿,所以我一般只启用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,不符合使用要求。

img4.gif

showhud & showfps

showhud命令会在keyWindow上展示一个debug视图,显示内存占用,CPU使用率,主线程FPS。

img5.jpg

点击debug视图会present一个新的view controller(如下图),里面的功能后文会有介绍。


img6.jpg

showfps命令已被showhud替代,项目里保留下来这个极简的工具仅供学习参考。

sandbox

present一个沙盒浏览器,可以查看主bundle目录和数据目录,具有系统分享(如隔空投送)、删除文件等功能。
这里用gif演示(首次调用命令要等待几秒,为了减少gif大小,演示的是初始化后的调用)

img7.gif

这是拉起系统分享的一个例子:
share_AirDrop.jpg

inspect

查看当前页面的UIView对象。还能看到常见的类如UILabel的关键属性值。


img8.jpg

request

自动打印http/https请求(WKWebView除外)。
可能会多次打印同一个请求,请自行判断。

request.gif

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即时编译并不稳定,命令报错是常见的。如果出现问题,请按以下步骤依次检查。

  1. pull最新代码,核对Xcode版本,HMLLDB一般只适配最新的Xcode版本
  2. 打开~/.lldbinit文件,确保是在末尾导入的HMLLDB.py文件令其命令不被覆盖。
  3. 启动APP后,主动点击Pause program execution进入LLDB调试模式执行命令,而不是用命中断点的方式执行命令。(除了部分命令)
  4. 重启Xcode。这一般能解决绝大多数问题。
  5. 重启电脑。有些命令出错会把电脑的LLDB服务玩坏。
  6. 完成以上步骤命令依然出错。请拷贝错误内容发布到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有足够高的权限,开发新功能除了个人需求还取决于想象力,各位读者如果有什么好的建议和想法,欢迎留言提出来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354