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有足夠高的權限,開發新功能除了我的需求還取決於想象力,各位讀者若是有什麼好的建議和想法,歡迎留言提出來。

相關文章
相關標籤/搜索