LLDB命令库HMLLDB介绍

LLDB命令库HMLLDB介绍

前言

和大多数人同样,我首次接触到的LLDB命令是其自带的po命令,用于打印对象。后来我学习了更多的命令才发觉LLDB的强大,尤为是可用于动态修改的expression命令,我至今依然在大量使用,如自动填充帐号密码,log控制,mock控制,自动打印UIViewController的生命周期等等。
不限于此,苹果还提供了Scripting Bridge API,简称SB API,可用Python脚本控制LLDB,其中Facebook的chisel项目和Derek Selander的LLDB项目是比较知名LLDB命令库,其中大部分功能都是基于SB API。不过这两个项目还存在不少问题,对我影响较大的就是更新不及时和命令互斥,另外我本身也有一些想法,因而也开发了一个LLDB命令库,即HMLLDB,首要目的是提高开发和调试效率。ios

特点

  • 无侵入,绿色版,项目无需修改,git看不到任何相关记录。这也是全部LLDB命令的特色
  • 全部命令均支持真机和模拟器
  • 全部命令均支持Objective-C和Swift项目
  • 一些命令提供了应用内的交互式的UI

系统要求

  • Xcode 12.3
  • 64位模拟器或真机,iOS 9.0 +
  • Debug模式(或者Optimization Level设置为[-O0]/[-Onone])

安装

项目源码:github.com/chenhuimao/…git

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

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

  1. 重启Xcode,运行你本身的iOS项目,点击Pause program execution进入LLDB调试模式,输入命令help,若是看到有下文介绍的命令,代表安装成功。

命令介绍

主要命令的简要说明:express

Command Description
deletefile 删除沙盒里指定的文件
pbundlepath 打印主bundle的路径
phomedirectory 打印沙盒的路径,即"~"
fclass 查找并打印全部包含指定字符串的类名
fsubclass 查找并打印一个类的全部子类
fsuperclass 查找并打印一个类的父类
fmethod 在全部方法列表中查找并打印指定方法,也能够打印指定类的方法列表
methods 至关于调用[input _methodDescription],打印类的全部方法
properties 至关于调用[input _propertyDescription],打印类的全部property
ivars 至关于调用[input _ivarDescription],打印类实例的ivar
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输出以下:swift

(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调试模式执行下面的命令,而不是用命中断点的方式执行命令。浏览器

deletefile

开发中经常须要删除沙盒中的某些文件以重置状态,deletefile命令能够快速删除指定路径的文件夹。有些数据还留在内存中,因此建议删除后当即从新运行项目。这是我最常常调用的命令之一。markdown

# 删除沙盒里全部的文件
(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查找并打印全部包含指定字符串的类名,不区分大小写。app

(lldb) fclass NormalLoadingViewController
[HMLLDB] Waiting...
[HMLLDB] Count: 1 
Kingfisher_Demo.NormalLoadingViewController (0x102148fa8)

# 不区分大小写,包含匹配
(lldb) fclass Kingfisher_Demo.im
[HMLLDB] Waiting...
[HMLLDB] Count: 2 
Kingfisher_Demo.ImageDataProviderCollectionViewController (0x102149a18)
Kingfisher_Demo.ImageCollectionViewCell (0x1021498e8)
复制代码

fsubclass查找并打印一个类的全部子类。ide

(lldb) fsubclass UICollectionViewController
[HMLLDB] Waiting...
[HMLLDB] Subclass count: 10 
Kingfisher_Demo.InfinityCollectionViewController
Kingfisher_Demo.HighResolutionCollectionViewController
...
复制代码

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 (0x10526f1c0)
	Type encoding:v16@0:8
	Class:Kingfisher.ImageCache
(-) accessibilityClearInternalData (0x1084ffd08)
	Type encoding:v16@0:8
	Class:NSObject
(+) _accessibilityClearProcessedClasses: (0x7fff2dc2cd25)
	Type encoding:v24@0:8@16
	Class:NSObject
...

# 选项-c:打印指定类的方法列表,区分大小写
(lldb) fmethod -c ImageCache
[HMLLDB] Waiting...
[HMLLDB] Class: Kingfisher.ImageCache (0x1052f26f8)
Instance methods count: 3. Class method count: 0.
(-) cleanExpiredDiskCache (0x10526fa00)
	Type encoding:v16@0:8
(-) backgroundCleanExpiredDiskCache (0x105271330)
	Type encoding:v16@0:8
(-) clearMemoryCache (0x10526f1c0)
	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)
		- (long) collectionView:(id)arg1 numberOfItemsInSection:(long)arg2; (0x10d522a20)
		- (void) collectionView:(id)arg1 willDisplayCell:(id)arg2 forItemAtIndexPath:(id)arg3; (0x10d523af0)
		- (void) collectionView:(id)arg1 didEndDisplayingCell:(id)arg2 forItemAtIndexPath:(id)arg3; (0x10d522cb0)
		- (id) initWithCoder:(id)arg1; (0x10d522960)
...

# 这3个命令只能用于NSObject的子类
(lldb) methods KingfisherManager
[HMLLDB] KingfisherManager is not a subclass of NSObject

复制代码

plifecycle

用于打印UIViewController的生命周期。无侵入的方式,加上Xcode能够设置Console字体颜色使输出一目了然,成为了我最喜好的命令之一。
使用方法:

  1. 新增一个符号断点Symbolic Breakpoint,在Symbol一行添加须要打印的方法,如-[UIViewController viewDidAppear:]
  2. 添加一个Action(Debugger Command),在里面新增plifecycle命令
  3. 勾上这个选项: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一个沙盒浏览器,具备系统分享、删除文件功能。将来可能会增长部分文件的预览功能。 这里用gif演示(首次调用命令要等待几秒,为了减小gif大小,演示的是初始化后的调用)

inspect

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

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] [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竟然没有暴露出这个方法。
  • 即时编译速度慢。虽然有在Python中去hook OC的方法,按需执行相关代码,但仍是不够快。若是调试工具使用频率很高,仍是直接集成到项目中的传统方法效率更高。
  • 还没找到加载某些系统framework的方法。
  • 仅支持iOS平台。

将来的计划

毕竟是在Debug模式下的调试命令,上述问题并不是急迫的问题,因此将来主要仍是开发新的命令。理论上HMLLDB能够成为绿色版的FLEX,只是工程量浩大,除非我找到了方法能够真机运行时加载电脑本地路径的framework。 现有的命令大可能是结合我我的经历而开发的。LLDB有足够高的权限,开发新功能除了我的需求还取决于想象力,各位读者若是有什么好的建议和想法,欢迎留言提出来。

相关文章
相关标签/搜索