(十)自定義LLDB命令 基礎(chǔ)

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 aliascommand regex。下面,權(quán)衡了便利與復(fù)雜的就是腳本橋接(script bridging)。用它你可以做到幾乎你所有想做的事情。它是一個(gè)PythonLLDB調(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)行交互。

我們先來看看LLDBPython版本。

 ~> 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 addhelloworld模塊中的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_commandprint這一行。當(dāng)用Python來創(chuàng)建一個(gè)LLDB命令時(shí),會(huì)傳入幾個(gè)特別的參數(shù):debugger、commandresult

我們先來試試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> >

輸入cpdb恢復(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_getClassListclass_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);

或者你還可以使用LLDBgui命令。

(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.SBProcessSBTargetSBProcess有一對(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.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中NSObjectdescription方法。

再看看其他幾個(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

  1. 使用LLDB創(chuàng)建正則斷點(diǎn)。
  2. 添加一個(gè)斷點(diǎn)操作動(dòng)作執(zhí)行到當(dāng)前幀完成。
  3. 利用寄存器的知識(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)就包括SBFrameSBBreakpointLocationPython字典。

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)時(shí)類的交互

在斷點(diǎn)回調(diào)函數(shù)中,SBFrameSBBreakpointLocation是大多數(shù)重要lldb類的線索??梢酝ㄟ^SBFrameSBFrame對(duì)SBModule的引用來獲取所有主要類實(shí)例。

請(qǐng)記住,不要在腳本中使用lldb.frame或其他全局變量。因?yàn)樗鼈冊(cè)谀_本中執(zhí)行時(shí),信息可能比較落后。因此必須遍歷以framebc_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)

斷點(diǎn)

我們已經(jīng)成功地創(chuàng)建了一個(gè)正則斷點(diǎn)命令?,F(xiàn)在,我們已經(jīng)停在了NSObject其中一個(gè)init方法,可能是類方法或?qū)嵗椒ā6疫@很可能是NSObject的一個(gè)子類。我們將使用LLDBPython腳本中手動(dòng)地重現(xiàn)這個(gè)操作。

我們結(jié)束執(zhí)行這個(gè)方法。因?yàn)槲覀兪褂玫氖?code>tvOS模擬器,它的架構(gòu)是x64,所以我們需要使用RAX寄存器打印出LLDBNSObjectinit的返回值。

(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()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 為什么會(huì)有腳本橋接 在LLDB中有很多方式可以創(chuàng)建自定義命令。第一種便是command alias,它為一個(gè)...
    blueshadow閱讀 7,100評(píng)論 2 10
  • [轉(zhuǎn)]淺談LLDB調(diào)試器文章來源于:http://www.cocoachina.com/ios/20150126/...
    loveobjc閱讀 2,735評(píng)論 2 6
  • 與調(diào)試器共舞 - LLDB 的華爾茲 nangege 2014/12/19 你是否曾經(jīng)苦惱于理解你的代碼,而去嘗試...
    McDan閱讀 951評(píng)論 0 0
  • 你是否曾經(jīng)苦惱于理解你的代碼,而去嘗試打印一個(gè)變量的值? NSLog(@"%@", whatIsInsideThi...
    木易林1閱讀 1,043評(píng)論 0 4
  • 隨著Xcode 5的發(fā)布,LLDB調(diào)試器已經(jīng)取代了GDB,成為了Xcode工程中默認(rèn)的調(diào)試器。它與LLVM編譯器一...
    隨風(fēng)飄蕩的小逗逼閱讀 1,461評(píng)論 0 0

友情鏈接更多精彩內(nèi)容