1. 自定義LLDB命令
我們已經(jīng)學(xué)了一些基礎(chǔ)的LLDB命令?,F(xiàn)在是時(shí)候吧這些只是組合起來創(chuàng)造一些強(qiáng)力的復(fù)雜調(diào)試腳本了。LLDB允許你通過Python來進(jìn)行大部分調(diào)試,輔助你解開那些隱藏在背后的秘密。
1.1 腳本橋接
LLDB有幾種方法可以自定的命令。之前我們學(xué)習(xí)了command alias和command regex。下面,權(quán)衡了便利與復(fù)雜的就是腳本橋接(script bridging)。用它你可以做到幾乎你所有想做的事情。它是一個(gè)Python到LLDB調(diào)試器的接口,用來擴(kuò)展調(diào)試功能,完成更復(fù)雜的調(diào)試需求。
首先必須要提到一個(gè)腳本:
/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/Python/lldb/macosx/heap.py
這個(gè)腳本做了這些事情:可以找到調(diào)用棧中所有malloc的對(duì)象(malloc_info -s);可以獲取所有NSObject特定子類的所有示例(obj_refs -O);可以找到所有指向特定內(nèi)存地址的指針(ptr_refs);找到內(nèi)存中的所有C字符串(cstr_ref)。
你可以通過下面的方式來加載這個(gè)腳本。
command script import lldb.macosx.heap
然而,這個(gè)腳本因?yàn)榫幾g器改變而代碼沒有改變,導(dǎo)致它有一些功能沒法使用了。
Python 101
LLDB腳本橋接是Python到調(diào)試器的接口。你可以在LLDB中加載并執(zhí)行Python腳本。在這些Python腳本中,你需要導(dǎo)入lldb模塊來和調(diào)試器進(jìn)行交互。
我們先來看看LLDB的Python版本。
~> lldb
(lldb) script import sys
(lldb) script print (sys.version)
3.7.3 (default, Dec 13 2019, 19:58:14)
[Clang 11.0.0 (clang-1100.0.33.17)]
~> python3 --version
Python 3.7.3
在Python中玩一玩
~> python3
Python 3.7.3 (default, Dec 13 2019, 19:58:14)
[Clang 11.0.0 (clang-1100.0.33.17)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> h = "hellow world"
>>> h
'hellow world'
>>> h.split(" ")
['hellow', 'world']
如果你想查看Python類的類型,加上.__class__就行了。
>>> h.split(" ").__class__
<class 'list'>
>>> h.__class__
<class 'str'>
如果你想查看幫助文檔,利用help命令就可以了。
>>> help (str)
>>> help (str.split)
如果你想定義一個(gè)函數(shù)怎么做呢?
>>> def test(a):
...
省略號(hào)表示你開始創(chuàng)建一個(gè)函數(shù)了。輸入兩個(gè)空格,然后輸入print(a + " world!")。Python是通過縮進(jìn)來判斷作用域的,如果你的縮進(jìn)不對(duì),Python的函數(shù)是會(huì)報(bào)錯(cuò)的。再次點(diǎn)擊回車來退出函數(shù)的編寫。
>>> def test(a):
... print(a + " world!")
...
>>> test("hello")
hello world!
創(chuàng)建你的第一個(gè)LLDB Python腳本
首先我們創(chuàng)建一個(gè)文件夾~/lldb。
~> mkdir ~/lldb
你喜歡用什么編輯器都可以,創(chuàng)建~/lldb/helloworld.py。
def your_first_command(debugger, command, result, internal_dict):
print ("hello world!")
函數(shù)中的參數(shù),你可以先不管,就是由LLDB傳過來的參數(shù)。
~> lldb
(lldb) command script import ~/lldb/helloworld.py
我們?cè)?code>LLDB中導(dǎo)入這個(gè)文件,如果沒有問題的話,什么輸出都不會(huì)有。因?yàn)樗皇前盐募蚪舆^來了。如果你要調(diào)用里面函數(shù)怎么辦呢?你首先需要導(dǎo)入對(duì)應(yīng)的模塊。
(lldb) script import helloworld
你可以通過列出模塊匯總所有函數(shù)來驗(yàn)證是否導(dǎo)入成功。
(lldb) script dir(helloworld)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'your_first_command']
在里面看到了我們定義的your_first_command方法。
那怎么在LLDB中調(diào)用這個(gè)函數(shù)呢?
(lldb) command script add -f helloworld.your_first_command yay
(lldb) yay
hello world!
我們通過
command script add把helloworld模塊中的your_first_command方法定義為LLDB命令yay了。-f表示你要添加的是一個(gè)python方法。
更有效地設(shè)置命令
如果你寫了很多自定義腳本了,你肯定不希望每次LLDB啟動(dòng)的時(shí)候,都靠自己來進(jìn)行導(dǎo)入。幸好,LLDB有一個(gè)叫__lldb_init_module的模塊,來幫助你進(jìn)行加載。
我們?cè)?code>helloworld.py追加一下代碼。
def your_first_command(debugger, command, result, internal_dict):
print ("hello world!")
#LLDB加載時(shí)會(huì)自動(dòng)執(zhí)行
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f helloworld.your_first_command yay')
你傳入了一個(gè)debugger就是SBDebugger的一個(gè)實(shí)例,然后你調(diào)用了它的HandleCommand方法。這個(gè)方法和在LLDB進(jìn)行輸入的效果差不多。
保存helloworld.py。然后在~/.lldbinit中添加導(dǎo)入代碼。
command script import ~/lldb/helloworld.py
然后在終端新的tab中啟動(dòng)lldb。
~> lldb
(lldb) yay
hello world!
1.2 調(diào)試腳本橋接
用pdb調(diào)試你的調(diào)試腳本
在helloworld.py腳本your_first_command改成下面這樣。
def your_first_command(debugger, command, result, internal_dict):
import pdb; pdb.set_trace()
print ("hello world!")
然后啟動(dòng)LLDB。
~> lldb
(lldb) yay woot
> /Users/xxx/lldb/helloworld.py(3)your_first_command()
-> print ("hello world!")
(Pdb)
我們可以看到pdb已經(jīng)斷在了your_first_command中print這一行。當(dāng)用Python來創(chuàng)建一個(gè)LLDB命令時(shí),會(huì)傳入幾個(gè)特別的參數(shù):debugger、command和result。
我們先來試試command。這個(gè)命令會(huì)列出你傳給yay命令的所有命令。因?yàn)檫@里沒有處理任何命令的邏輯,所以yay會(huì)自動(dòng)忽略這些輸入。
-> print ("hello world!")
(Pdb) command
'woot'
我們?cè)賮砜纯?code>result。輸出是一個(gè)SBCommandReturnObject實(shí)例對(duì)象。你可以通過它知道代碼在LLDB命令中執(zhí)行是否成功。另外,你可以添加一些信息。這些信息會(huì)在命令執(zhí)行完畢時(shí)顯示出來。
(Pdb) result
<lldb.SBCommandReturnObject; proxy of <Swig Object of type 'lldb::SBCommandReturnObject *' at 0x10cd4ce10> >
result.AppendMessage("2nd hello world!")
(Pdb) result.AppendMessage("2nd hello world!")
我們先還是保持?jǐn)嘧〉?。先保持這樣,我們來看看debugger。輸出是一個(gè)SBDebugger的實(shí)例對(duì)象。
(Pdb) debugger
<lldb.SBDebugger; proxy of <Swig Object of type 'lldb::SBDebugger *' at 0x10cf42a20> >
輸入c讓pdb恢復(fù)運(yùn)行。
(Pdb) c
hello world!
2nd hello world!
pdb死后調(diào)試
根據(jù)錯(cuò)誤類型,pdb有一個(gè)很吸引人的選項(xiàng),讓你可以探究問題發(fā)生時(shí)的調(diào)用棧,但它僅在發(fā)生異常時(shí)有效。
這里我們重新建一個(gè)python文件findclass.py。
import lldb
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f findclass.findclass findclass')
def findclass(debugger, command, result, internal_dict):
"""
The findclass command will dump all the Objective-C runtime classes it knows about.
Alternatively, if you supply an argument for it, it will do a case sensitive search
looking only for the classes which contain the input.
Usage: findclass # All Classes
Usage: findclass UIViewController # Only classes that contain UIViewController in name
"""
codeString = r'''
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class c = classes[i];
[returnString appendFormat:@"%s,", class_getName(c)];
}
free(classes);
returnString;
'''
res = lldb.SBCommandReturnObject()
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
if res.GetError():
raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
elif not res.HasResult():
raise AssertionError("There's no result. Womp womp....")
returnVal = res.GetOutput()
resultArray = returnVal.split(",")
if not command: # No input supplied
print (returnVal.replace(",", "\n").replace("\n\n\n", ""))
else:
filteredArray = filter(lambda className: command in className, resultArray)
filteredResult = "\n".join(filteredArray)
result.AppendMessage(filteredResult)
下面我們用LLDB調(diào)試Photos。
~> lldb -n Photos
(lldb) command script import ~/lldb/findclass.py
(lldb) help findclass
For more information run 'help findclass' Expects 'raw' input (see 'help
raw-input'.)
Syntax: findclass
The findclass command will dump all the Objective-C runtime classes it
knows about.
Alternatively, if you supply an argument for it, it will do a case
sensitive search
looking only for the classes which contain the input.
Usage: findclass # All Classes
Usage: findclass UIViewController # Only classes that contain
UIViewController in name
(lldb) findclass
Traceback (most recent call last):
File "/Users/ycpeng/lldb/findclass.py", line 40, in findclass
raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]
這個(gè)腳本的作者提供信息沒啥用,但至少它拋出了一個(gè)異常。我們就可以用pdb查看錯(cuò)誤發(fā)生時(shí)的調(diào)用棧。
(lldb) script import pdb
(lldb) findclass
Traceback (most recent call last):
File "/Users/ycpeng/lldb/findclass.py", line 40, in findclass
raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]
(lldb) script pdb.pm()
> /Users/ycpeng/lldb/findclass.py(40)findclass()
-> raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
我們甚至可以在pdb中查看源代碼。
# 表示列出1~50行代碼
(Pdb) l 1, 50
其中18~35行是一個(gè)長(zhǎng)字符串,就是這個(gè)命令的核心邏輯。
# 直接打印,可能不太好看
(Pdb) codeString
'\n @import Foundation;\n int numClasses;\n Class * classes = NULL;\n classes = NULL;\n numClasses = objc_getClassList(NULL, 0);\n NSMutableString *returnString = [NSMutableString string];\n classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);\n numClasses = objc_getClassList(classes, numClasses);\n\n for (int i = 0; i < numClasses; i++) {\n Class c = classes[i];\n [returnString appendFormat:@"%s,", class_getName(c)];\n }\n free(classes);\n \n returnString;\n '
# 我們用print打印,就會(huì)好看很多
(Pdb) print(codeString)
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class c = classes[i];
[returnString appendFormat:@"%s,", class_getName(c)];
}
free(classes);
returnString;
我們可以看到這是一段OC代碼。通過運(yùn)行時(shí)獲取所有的類。
我們來看一下報(bào)錯(cuò)的地方。我們還能看到40行的斷點(diǎn)->。
37 res = lldb.SBCommandReturnObject()
38 debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
39 if res.GetError():
40 -> raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
41 elif not res.HasResult():
42 raise AssertionError("There's no result. Womp womp....")
我們來看看這里res.GetError()到底報(bào)了什么錯(cuò)。
(Pdb) print(res.GetError())
error: warning: got name from symbols: classes
error: 'objc_getClassList' has unknown return type; cast the call to its declared return type
error: 'objc_getClassList' has unknown return type; cast the call to its declared return type
error: 'class_getName' has unknown return type; cast the call to its declared return type
這個(gè)錯(cuò)誤看起來就就像平時(shí)LLDB打印出來錯(cuò)誤的樣子了。我們可以看到objc_getClassList和class_getName返回了未知類型。
我們查看一下文檔:
int objc_getClassList(Class *buffer, int bufferCount);
const char * class_getName(Class cls);
然后對(duì)應(yīng)強(qiáng)轉(zhuǎn)一下函數(shù)的返回類型:
codeString = r'''
@import Foundation;
int numClasses;
Class * classes = NULL;
classes = NULL;
numClasses = (int)objc_getClassList(NULL, 0);
NSMutableString *returnString = [NSMutableString string];
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = (int)objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class c = classes[i];
[returnString appendFormat:@"%s,", (char *)class_getName(c)];
}
free(classes);
returnString;
'''
保存代碼之后,Ctrl + D退出pdb。
(lldb) command script import ~/lldb/findclass.py
(lldb) findclass
//打印了很多OC類
我們也可以限制一下我們關(guān)心的類型。比如下面我們打印包含ViewController的類。
(lldb) findclass ViewController
NSServiceViewControllerUnifyingProxy
IPXFeedViewControllerSpec
IPXFeedViewControllerMacSpec
...
expression的調(diào)試選項(xiàng)
expression有個(gè)調(diào)試選項(xiàng)--debug或者-g。
在findclass.py中38行
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -O -- " + codeString, res)
加入調(diào)試選項(xiàng)-g
debugger.GetCommandInterpreter().HandleCommand("expression -lobjc -g -O -- " + codeString, res)
然后再執(zhí)行一下
(lldb) command script import ~/lldb/findclass.py
(lldb) findclass
Traceback (most recent call last):
File "/Users/ycpeng/lldb/findclass.py", line 40, in findclass
raise AssertionError("Uhoh... something went wrong, can you figure it out? :]")
AssertionError: Uhoh... something went wrong, can you figure it out? :]
Process 14327 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal 2147483647
frame #0: 0x000000010b342000 $__lldb_expr1`$__lldb_expr($__lldb_arg=0x0000000000000000) at lldb-566369.expr:42
39
40 void
41 $__lldb_expr(void *$__lldb_arg)
-> 42 {
43 ;
44 /*LLDB_BODY_START*/
45
Target 0: (Photos) stopped.
現(xiàn)在你就可以用LLDB進(jìn)行調(diào)試了。你需要查看源代碼需要用到命令source list,或者list,甚至更簡(jiǎn)單l。
(lldb) l
46 @import Foundation;
47 int numClasses;
48 Class * classes = NULL;
49 classes = NULL;
50 numClasses = (int)objc_getClassList(NULL, 0);
51 NSMutableString *returnString = [NSMutableString string];
52 classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
或者你還可以使用LLDB的gui命令。
(lldb) gui

接下來你就可以點(diǎn)擊N鍵進(jìn)行單步調(diào)試,用S鍵進(jìn)行step into了。如果你調(diào)試完畢,記得把-g刪掉。
1.3 lldb模塊中重要的類
-
lldb.SBDebugger:你需要用這個(gè)類來訪問你在調(diào)試腳本中創(chuàng)建的其他類。一個(gè)函數(shù)中一定會(huì)有一個(gè)對(duì)這個(gè)類實(shí)例對(duì)象的引用。這個(gè)類負(fù)責(zé)處理輸入到LLDB中的命令,而且可以控制在哪兒或者怎么展示輸出。 -
lldb.SBTarget:負(fù)責(zé)在內(nèi)存中調(diào)試的可執(zhí)行文件、調(diào)試文件和駐留在磁盤上的可執(zhí)行文件的物理文件。調(diào)試的時(shí)候,你要用SBDebugger的實(shí)例來選擇SBTarget實(shí)例,之后你就可以通過它訪問其他類了。 -
lldb.SBProcess:SBTarget與SBProcess有一對(duì)多關(guān)系:SBTarget管理一個(gè)或多個(gè)SBProcess實(shí)例。SBProcess負(fù)責(zé)內(nèi)存訪問以及進(jìn)程中的多線程。 -
lldb.SBThread:管理該特定線程中的棧幀SBFrames,還管理單步執(zhí)行的控制邏輯。 -
lldb.SBFrame:管理本地變量(通過調(diào)試信息給出)以及凍結(jié)在該特定幀上的任何寄存器。 -
lldb.SBModule:表示特定的可執(zhí)行文件。模塊可以包含主可執(zhí)行文件或任何動(dòng)態(tài)加載的代碼(如基礎(chǔ)框架)。可以使用image list命令獲得加載到可執(zhí)行文件中的模塊的完整列表。 -
lldb.SBFunction:表示一個(gè)加載到內(nèi)存中的泛型函數(shù)。這個(gè)類與SBFrame類有一對(duì)一的關(guān)系。
主要的LLDB Python類
通過LLDB來探索lldb模塊
我們每次修改~/.lldbinit都要輸入command source ~/.lldbinit比較麻煩。我們?cè)?code>~/.lldbinit添加一個(gè)命令。
command alias reload_script command source ~/.lldbinit
我們新建一個(gè)tvOS的項(xiàng)目Meh,語(yǔ)言選Swift。設(shè)置一個(gè)斷點(diǎn),并輸入lldb.debugger。

LLDB有幾個(gè)可以方便訪問的全局變量:
lldb.SBDebugge -> lldb.debugger
lldb.SBTarget -> lldb.target
lldb.SBProcess -> lldb.process
lldb.SBThread -> lldb.thread
lldb.SBFrame -> lldb.frame
我們可以看看我們當(dāng)前的target
//直接打印這個(gè)類可能看不出什么
(lldb) script lldb.target
<lldb.SBTarget; proxy of <Swig Object of type 'lldb::SBTarget *' at 0x11556d420> >
//我們來print一下
(lldb) script print(lldb.target)
Meh
print命令可以打印一個(gè)實(shí)例的概況。就像po命令調(diào)用了OC中NSObject的description方法。
再看看其他幾個(gè)命令的打印效果。
// 打印當(dāng)前進(jìn)程的信息
(lldb) script print(lldb.process)
SBProcess: pid = 14955, state = stopped, threads = 8, executable = Meh
// 打印當(dāng)前線程的信息,還有我們的斷點(diǎn)
(lldb) script print(lldb.thread)
thread #1: tid = 0xc525e, 0x000000010e81fad0 Meh`ViewController.viewDidLoad(self=0x00007fe7dfe04b10) at ViewController.swift:12, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
// 打印當(dāng)前棧幀的信息
(lldb) script print(lldb.frame)
frame #0: 0x000000010e81fad0 Meh`ViewController.viewDidLoad(self=0x00007fe7dfe04b10) at ViewController.swift:12
學(xué)習(xí)和查看腳本橋接類的文檔
通過help命令可以查看幫助文檔。
(lldb) script help(lldb.target)
(lldb) script help(lldb.SBTarget)
為了方便閱讀,我們可以在~/.lldbinit添加:
command regex gdocumentation 's/(.+)/script import os; os.system("open https:" + unichr(47) + unichr(47) + "lldb.llvm.org" + unichr(47) + "python_reference" + unichr(47) + "lldb.%1-class.html")/'
輸入我們要查詢的東西,然后直接就可以跳轉(zhuǎn)到對(duì)應(yīng)的網(wǎng)址了
(lldb) gdocumentation SBTarget
創(chuàng)建breakAfterRegex命令
如何設(shè)計(jì)一個(gè)命令,在函數(shù)之后立即停止,打印出返回值,然后繼續(xù)?
我們來創(chuàng)建一個(gè)~/lldb/BreakAfterRegex.py。
- 使用LLDB創(chuàng)建正則斷點(diǎn)。
- 添加一個(gè)斷點(diǎn)操作動(dòng)作執(zhí)行到當(dāng)前幀完成。
- 利用寄存器的知識(shí)打印出正確的寄存器中的返回值。
import lldb
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f BreakAfterRegex.breakAfterRegex bar')
def breakAfterRegex(debugger, command, result, internal_dict):
print ("yay. basic script setup with input: {}".format(command))
這里添加了一個(gè)名為bar的命令。該命令由模塊BreakAfterRegex中的breakAfterRegex實(shí)現(xiàn)。
在~/.lldbinit添加。
command script import ~/lldb/BreakAfterRegex.py
然后我們?cè)陧?xiàng)目中,輸入
(lldb) reload_script
(lldb) bar UIViewController test -a -b
yay. basic script setup with input: UIViewController test -a -b
新LLDB腳本中的輸出是提供給它的參數(shù)?;竟羌芤呀?jīng)準(zhǔn)備好了?,F(xiàn)在是編寫基于輸入創(chuàng)建斷點(diǎn)的代碼的時(shí)候了。返回BreakAfterRegex.py并找到def breakAfterRegex(debugger, command, result, internal_dict):。刪除print語(yǔ)句并將其替換為以下邏輯:
def breakAfterRegex(debugger, command, result, internal_dict):
#1 使用傳入的正則參數(shù)創(chuàng)建斷點(diǎn)。這個(gè)斷點(diǎn)對(duì)象將是SBBreakpoint類型。
target = debugger.GetSelectedTarget()
breakpoint = target.BreakpointCreateByRegex(command)
#2 如果斷點(diǎn)創(chuàng)建失敗,腳本將警告您它找不到任何可以中斷的地方。否則,將打印出斷點(diǎn)對(duì)象。
if not breakpoint.IsValid() or breakpoint.num_locations == 0:
result.AppendWarning("Breakpoint isn't valid or hasn't found any hits")
else:
result.AppendMessage("{}".format(breakpoint))
#3 設(shè)置斷點(diǎn),以便每當(dāng)斷點(diǎn)命中時(shí)的回調(diào)函數(shù)。
breakpoint.SetScriptCallbackFunction("BreakAfterRegex.breakpointHandler")
def breakpointHandler(frame, bp_loc, dict):
# 獲取函數(shù)名,并打印
function_name = frame.GetFunctionName()
print("stopped in: {}".format(function_name))
return True
注意在函數(shù)的末尾返回
True。返回True將導(dǎo)致程序停止執(zhí)行。返回False,甚至省略return語(yǔ)句都會(huì)導(dǎo)致程序在執(zhí)行此方法后繼續(xù)運(yùn)行。
為斷點(diǎn)創(chuàng)建回調(diào)函數(shù)時(shí),要實(shí)現(xiàn)的方法簽名不同。剛剛的回調(diào)就包括SBFrame、SBBreakpointLocation和Python字典。
SBFrame表示已停在其中的棧幀。SBBreakpointLocation是在SBBreakpoint中找到一個(gè)斷點(diǎn)的實(shí)例。這是非常有意義的。因?yàn)閷?duì)于一個(gè)斷點(diǎn),可能有很多點(diǎn)擊。特別是如果嘗試中斷一個(gè)經(jīng)常實(shí)現(xiàn)的函數(shù),例如main,或者使用了會(huì)匹配很多結(jié)果的正則表達(dá)式。
下面顯示了當(dāng)在特定函數(shù)上停止時(shí),類的簡(jiǎn)化交互:

在斷點(diǎn)回調(diào)函數(shù)中,SBFrame和SBBreakpointLocation是大多數(shù)重要lldb類的線索??梢酝ㄟ^SBFrame或SBFrame對(duì)SBModule的引用來獲取所有主要類實(shí)例。
請(qǐng)記住,不要在腳本中使用
lldb.frame或其他全局變量。因?yàn)樗鼈冊(cè)谀_本中執(zhí)行時(shí),信息可能比較落后。因此必須遍歷以frame或bc_loc開頭的變量才能獲取到所需類的實(shí)例。
我們來試一下。
(lldb) reload_script
(lldb) bar NSObject.init\]
SBBreakpoint: id = 3, regex = 'NSObject.init\]', locations = 2
繼續(xù)執(zhí)行,并使用tvOS模擬器的遠(yuǎn)程遙控器點(diǎn)擊一下觸發(fā)斷點(diǎn)。如果在觸發(fā)斷點(diǎn)時(shí)遇到問題,一種可靠的方法是導(dǎo)航到模擬器的主屏幕(? + Shift + H)。

我們已經(jīng)成功地創(chuàng)建了一個(gè)正則斷點(diǎn)命令?,F(xiàn)在,我們已經(jīng)停在了NSObject其中一個(gè)init方法,可能是類方法或?qū)嵗椒ā6疫@很可能是NSObject的一個(gè)子類。我們將使用LLDB在Python腳本中手動(dòng)地重現(xiàn)這個(gè)操作。
我們結(jié)束執(zhí)行這個(gè)方法。因?yàn)槲覀兪褂玫氖?code>tvOS模擬器,它的架構(gòu)是x64,所以我們需要使用RAX寄存器打印出LLDB中NSObject的init的返回值。
(lldb) finish
(lldb) po $rax
<UIViewControllerBuiltinTransitionViewAnimator: 0x600001c82520>
打開BreakAfterRegex.py并重寫breakpointHandler函數(shù)。
def breakpointHandler(frame, bp_loc, dict):
#1 文檔信息
'''The function called when the regularexpression breakpoint gets triggered'''
#2 從SBFrame出發(fā),通過引用獲取到SBDebugger和SBThread的實(shí)例
thread = frame.GetThread()
process = thread.GetProcess()
debugger = process.GetTarget().GetDebugger()
#3 獲取父函數(shù)的名稱
function_name = frame.GetFunctionName()
#4 讓調(diào)試器不要異步執(zhí)行
debugger.SetAsync(False)
#5 退出這個(gè)方法,將不再處于當(dāng)前的棧幀中
thread.StepOut()
#6 調(diào)用evaluateReturnedObject方法獲取適當(dāng)?shù)妮敵鲂畔? output = evaluateReturnedObject(debugger, thread, function_name)
if output is not None:
print(output)
return False
下面我們來實(shí)現(xiàn)evaluateReturnedObject方法。
def evaluateReturnedObject(debugger, thread, function_name):
'''Grabs the reference from the return register and returns a string from the evaluated value.
TODO ObjC only
'''
#1 實(shí)例化SBCommandReturnObject
res = lldb.SBCommandReturnObject()
#2 獲取一些后面要用的實(shí)例
interpreter = debugger.GetCommandInterpreter()
target = debugger.GetSelectedTarget()
frame = thread.GetSelectedFrame()
parent_function_name = frame.GetFunctionName()
#3 創(chuàng)建要執(zhí)行的表達(dá)式,該表達(dá)式將輸出返回值
expression = 'expression -lobjc -O -- {}'.format(getRegisterString(target))
#4 通過SBCommandInterpreter執(zhí)行表達(dá)式
# 它允許我們控制輸出的位置,而不是立即將其傳遞到stderr或stdout。
interpreter.HandleCommand(expression, res)
#5 查看執(zhí)行表達(dá)式之后是否有返回值
if res.HasResult():
#6 把斷住的函數(shù)名、寄存器獲取的對(duì)象和前一幀的函數(shù)名,格式化為字符串并返回該字符串。
output = '{}\nbreakpoint: {}\nobject: {}\nstopped: {}'.format(
'*' * 80,
function_name,
res.GetOutput().replace('\n', ''),
parent_function_name)
return output
else:
#7 如果不需要輸出,返回None
return None
還剩寄存器讀取沒有完成。
# 根據(jù)架構(gòu)返回需要讀取的寄存器名
def getRegisterString(target):
triple_name = target.GetTriple()
if "x86_64" in triple_name:
return "$rax"
elif "i386" in triple_name:
return "$eax"
elif "arm64" in triple_name:
return "$x0"
elif "arm" in triple_name:
return "$r0"
raise Exception('Unknown hardware. Womp womp')
我們現(xiàn)在來試試!reload_script重新加載腳本,再刪除現(xiàn)在的所有斷點(diǎn),重新執(zhí)行bar NSObject.init\]。然后continue幾次,可以看到一些有用的信息了。
(lldb) reload_script
(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n]
All breakpoints removed. (3 breakpoints)
(lldb) bar NSObject.init\]
SBBreakpoint: id = 4, regex = 'NSObject.init\]', locations = 2
********************************************************************************
breakpoint: -[NSObject init]
object: <RBSXPCMessageReply: 0x6000012e1240>
stopped: -[RBSXPCMessageReply _initWithMessage:]
********************************************************************************
breakpoint: -[NSObject init]
object: 105553124551936
stopped: -[BSXPCCoder initWithMessage:]
********************************************************************************
breakpoint: -[NSObject init]
object: 105553124551936
stopped: objc_object::sidetable_retain()
