iOS開(kāi)發(fā)調(diào)試 - LLDB使用概覽

前言


LLDB是個(gè)開(kāi)源的內(nèi)置于XCode的具有REPL(read-eval-print-loop)特征的Debugger垃瞧,其可以安裝C++或者Python插件蔫劣。在日常的開(kāi)發(fā)和調(diào)試過(guò)程中給開(kāi)發(fā)人員帶來(lái)了非常多的幫助鸵隧。

(lldb)po std.name
Noskthing

了解并熟練掌握LLDB的使用是非常有必要的。這篇文章將會(huì)為大家總結(jié)日常高頻使用的一些技巧瓦哎。文章分節(jié)的主要依據(jù)是功能的相關(guān)性,并且省略了很多Xcode已經(jīng)集成并且可視化的操作瞬哼。

  • 一些基礎(chǔ)
  • 斷點(diǎn)相關(guān)
  • 參數(shù)檢查
  • expression指令
  • hook的概念
  • 流程控制
  • script
  • image
  • register
  • 結(jié)語(yǔ)

一些基礎(chǔ)


LLDB的基本語(yǔ)法如下

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]

其中內(nèi)置了非常多的功能把跨,選擇去硬背每一條指令并不是一個(gè)明智的選擇秀姐。我們只需要記住一些常用的指令,在需要的時(shí)候通過(guò)help命令來(lái)查看相關(guān)的描述即可面哼。

(lldb)help
Debugger commands:

  apropos           -- List debugger commands related to a word or subject.
  breakpoint        -- Commands for operating on breakpoints (see 'help b' for shorthand.)
  ...

還可以通過(guò)apropos來(lái)獲取具體命令的合法參數(shù)信息以及含義

(lldb) apropos breakpoint
The following commands may relate to 'breakpoint':
  _regexp-break                         -- Set a breakpoint using one of
                                           several shorthand formats.
  _regexp-tbreak                        -- Set a one-shot breakpoint using one
                                           of several shorthand formats.
  ...

斷點(diǎn)相關(guān)


Xcode本身已經(jīng)將大部分的操作用UI展示了出來(lái),比如說(shuō)

  • Breakpoint Navigator (? + 7)
  • Debug Navigator (? + 6)
  • Debug Area (? + Shift + Y)
  • Debug menu item


    斷點(diǎn)列表

日常開(kāi)發(fā)中大部分有關(guān)斷點(diǎn)的操作我們都可以不使用命令行直接通過(guò)Xcode的可視化操作來(lái)實(shí)現(xiàn),命令行的操作似乎是一種多余遣总。但是使用(lldb)help breakpoint查看一下LLDB提供的所有幫助圆米,你會(huì)發(fā)現(xiàn)在命令行中使用LLDB能夠給予我們更多更詳細(xì)的調(diào)試信息以及更廣闊的操作空間。

(lldb)help breakpoint

舉一個(gè)簡(jiǎn)單的例子首繁,我們需要為某一個(gè)函數(shù)設(shè)置一個(gè)斷點(diǎn)。比如說(shuō)給ViewController的VviewDidLoad方法設(shè)置一個(gè)斷點(diǎn)编检。這對(duì)于Xcode而言非常的簡(jiǎn)單伺糠。

添加斷點(diǎn)

編輯每一個(gè)斷點(diǎn)的各個(gè)選項(xiàng)也因?yàn)榭梢暬牟僮鞫兊梅浅5暮?jiǎn)單午绳。但是如果我們需要在系統(tǒng)調(diào)用的某個(gè)函數(shù)里設(shè)置斷點(diǎn)呢窍仰,抑或某個(gè)函數(shù)我們只能在crash log茫茫碌的堆棧信息里才能看到一點(diǎn)它的痕跡篷就,這個(gè)時(shí)候如何操作呢?

crash log

假設(shè)我們現(xiàn)在需要給objc_msgSend函數(shù)設(shè)置斷點(diǎn)近忙。首先先想辦法獲取objc_msgSend的地址竭业。我們?cè)贏ppdelegate.m文件給函數(shù)- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions打一個(gè)斷點(diǎn)智润,運(yùn)行程序如下圖所示。


我們可以通過(guò)(lldb)br set -a 0x0000000103c04ac0來(lái)為objc_msgSend()設(shè)置一個(gè)斷點(diǎn)未辆。輸入continue繼續(xù)執(zhí)行你會(huì)發(fā)現(xiàn)如果程序再次調(diào)用objc_msgSend()會(huì)暫停窟绷。
斷點(diǎn)設(shè)置

Tips
* 圖片是用模擬器運(yùn)行所以是x86,移動(dòng)設(shè)備是arm咐柜。兩者的指令有所不同兼蜈。圖中的callq指令對(duì)應(yīng)arm中的bl
* 由于 ASLR(地址空間配置隨機(jī)載入) 的原因地址是不固定的,所以圖中objc_msgSend()的地址在你的機(jī)器上是不可用的拙友。

斷點(diǎn)相關(guān)的指令很多很雜为狸,這里為大家列舉一些常用的。如果以后遇到一些特殊的需求遗契,可以借助help()指令來(lái)自行查找相關(guān)指令辐棒。

設(shè)置斷點(diǎn)

  • 給所有名為xx的函數(shù)設(shè)置一個(gè)斷點(diǎn)
(lldb)breakpoint set —name xx
(lldb)br s -n xx
(lldb)b xx
  • 在文件F指定行L設(shè)置斷點(diǎn)
(lldb)breakpoint set —file F —line L
(lldb)br s -f F -l L
(lldb)b F:L
  • 給所有名為xx的C++函數(shù)設(shè)置一個(gè)斷點(diǎn)(希望沒(méi)有同名的C函數(shù))
(lldb)breakpoint set —method xx
(lldb)br s -M xx
  • 給一個(gè)OC函數(shù)[objc msgSend:]設(shè)置一個(gè)斷點(diǎn)
(lldb)breakpoint set —name “[objc msgSend:]”
(lldb)b -n “[objc msgSend:]”
  • 給所有名為xx的OC方法設(shè)置一個(gè)斷點(diǎn)(希望沒(méi)有名為xx的C或者C++函數(shù))
(lldb)breakpoint set —selector xx
(lldb)br s -S count
  • 給所有函數(shù)名正則匹配成功的函數(shù)設(shè)置一個(gè)斷點(diǎn)
(lldb)breakpoint set --func-regex regular-expression
(lldb)br s -r regular-expression
  • 給指定函數(shù)地址func_addr的位置設(shè)置一個(gè)斷點(diǎn)
(lldb)br set -a func_addr

斷點(diǎn)查看

(lldb)breakpoint list
(lldb)br l

斷點(diǎn)刪除

(lldb)breakpoint delete index
(lldb)br del index

index指明斷點(diǎn)的序號(hào),如果為空則刪除所有斷點(diǎn)

watchpoint

iOS開(kāi)發(fā)當(dāng)中有一個(gè)重要的概念KVO牍蜂,我們會(huì)給一個(gè)重要的變量設(shè)置一個(gè)觀察者漾根,用以在它發(fā)生變化的時(shí)候做出相應(yīng)的操作。在調(diào)試過(guò)程中我們也可以借助LLDB來(lái)監(jiān)視某個(gè)變量或某一塊內(nèi)存的讀寫情況鲫竞。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSString * str = @"First";
    [self printString:str];
    str = @"Second";
    [self printString:str];
}

- (void)printString:(NSString *)str
{
    NSLog(@"%@",str);
}

我們利用watchpoint指令來(lái)監(jiān)視變量str立叛。需要重點(diǎn)說(shuō)明的是-w選項(xiàng),下例中并沒(méi)有寫出贡茅,缺省值是write秘蛇,這意味著只有在str被寫入的時(shí)候程序會(huì)暫停。

(lldb) watchpoint set variable str
Watchpoint created: Watchpoint 1: addr = 0x7fff5997f9e8 size = 8 state = enabled type = w
    declare @ '/Users/noskthing/Desktop/LLDBTest/LLDBTest/ViewController.m:22'
    watchpoint spec = 'str'
    new value: 0x0000000106280078
2017-07-22 17:35:13.534 LLDBTest[4585:521823] First

Watchpoint 1 hit:
old value: 0x0000000106280078
new value: 0x0000000106280098

(lldb) image lookup -a 0x0000000106280098
      Address: LLDBTest[0x0000000100003098] (LLDBTest.__DATA.__cfstring + 32)
      Summary: @"Second"
(lldb) image lookup -a 0x0000000106280078
      Address: LLDBTest[0x0000000100003078] (LLDBTest.__DATA.__cfstring + 0)
      Summary: @"First"

當(dāng)你輸入watchpoint list查看設(shè)置的watchpoint時(shí)系統(tǒng)會(huì)提示你當(dāng)前測(cè)試的機(jī)器允許設(shè)置的最大個(gè)數(shù)顶考。

(lldb) watchpoint list
Number of supported hardware watchpoints: 4
No watchpoints currently set.

參數(shù)檢查


當(dāng)我們調(diào)試程序遇到斷點(diǎn)的時(shí)候Xcode會(huì)自動(dòng)的將當(dāng)前作用域下的局部變量以及全局變量展示出來(lái)


當(dāng)前作用

借助命令行我們也能夠輕松的獲取這些參數(shù)的信息

  • 展示當(dāng)前作用域下的參數(shù)和局部變量
(lldb)frame variable
(lldb)fr v
  • 展示當(dāng)前作用域下的局部變量
(lldb)frame variable --no-args
(lldb)fr v -a
  • 展示指定變量var的具體內(nèi)容
(lldb)frame variable *var*
(lldb)fr v *var*
(lldb)p *var*
  • 展示當(dāng)前對(duì)象的全局變量
(lldb)target variable
(lldb)ta v

細(xì)心的朋友應(yīng)該能夠有所發(fā)現(xiàn)赁还,這些操作都有一個(gè)局限:我們查看的各個(gè)變量都是當(dāng)前作用域的。這意味著程序遇到斷點(diǎn)的時(shí)候暫停驹沿,所有的操作都是局限于當(dāng)前函數(shù)以及當(dāng)前函數(shù)所在線程的內(nèi)部艘策。可視化的操作并沒(méi)有給我們太多操作的空間渊季,但是借助命令行我們可以打破這樣一個(gè)局限朋蔫。

命令行輸入(lldb)thread backtrace可以獲取當(dāng)前線程函數(shù)的調(diào)用棧

(lldb)thread backtrace
* frame #0: 0x0000000100057204 test`-[ViewController viewDidLoad](self=0x000000014fe0ad10, _cmd=<unavailable>) at ViewController.m:99 [opt]
  frame #1: 0x000000018e1cfec0 UIKit`-[UIViewController loadViewIfRequired] + 1036
  frame #2: 0x000000018e1cfa9c UIKit`-[UIViewController view] + 28
  frame #3: 0x000000018e1d631c UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 76
  frame #4: 0x000000018e1d37b8 UIKit`-[UIWindow _setHidden:forced:] + 272
  frame #5: 0x000000018e245224 UIKit`-[UIWindow makeKeyAndVisible] + 48

輸入frame select指令我們可以任意的去選擇一個(gè)作用域去查看。

(lldb)frame select 2

類比f(wàn)rame的操作我們可以輕松看出線程選擇相關(guān)的操作

(lldb) thread list
Process 21035 stopped
* thread #1: tid = 0x27361a, 0x00000001892df224 libsystem_kernel.dylib`mach_msg_trap + 8, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  thread #2: tid = 0x273639, 0x00000001892fda88 libsystem_kernel.dylib`__workq_kernreturn + 8
  thread #3: tid = 0x27363a, 0x00000001893c2ca8 libsystem_pthread.dylib`start_wqthread
  thread #4: tid = 0x27363e, 0x00000001892fda88 libsystem_kernel.dylib`__workq_kernreturn + 8
  thread #5: tid = 0x27363f, 0x00000001892df224 libsystem_kernel.dylib`mach_msg_trap + 8, name = 'com.apple.uikit.eventfetch-thread'
  thread #6: tid = 0x273640, 0x00000001892fda88 libsystem_kernel.dylib`__workq_kernreturn + 8
  thread #7: tid = 0x273641, 0x00000001892fda88 libsystem_kernel.dylib`__workq_kernreturn + 8
  thread #8: tid = 0x273642, 0x00000001892fda88 libsystem_kernel.dylib`__workq_kernreturn + 8
  thread #10: tid = 0x273646, 0x00000001892df224 libsystem_kernel.dylib`mach_msg_trap + 8, name = 'com.apple.NSURLConnectionLoader'
  thread #11: tid = 0x273644, 0x00000001892df224 libsystem_kernel.dylib`mach_msg_trap + 8, name = 'AFNetworking'
  thread #12: tid = 0x27364a, 0x00000001892fda88 libsystem_kernel.dylib`__workq_kernreturn + 8
  thread #13: tid = 0x27364b, 0x00000001892fd23c libsystem_kernel.dylib`__select + 8, name = 'com.apple.CFSocket.private'
(lldb) thread select 2

以上提到的幾個(gè)指令意味著借助命令行却汉,我們可以在斷點(diǎn)發(fā)生的時(shí)候跳轉(zhuǎn)到當(dāng)前存在的任一線程里的任一作用域去進(jìn)行操作驯妄。

除卻frame的操作,我們很多時(shí)候習(xí)慣借助NSLog去打印某些關(guān)鍵的信息合砂。如果運(yùn)行到一半的時(shí)候發(fā)現(xiàn)漏寫了某個(gè)地方的NSLog青扔,加入相關(guān)代碼并重新運(yùn)行也許不是一個(gè)讓人省心的方法。我們可以在需要打印的地方設(shè)置一個(gè)斷點(diǎn),然后運(yùn)行p object或者po object指令來(lái)查看指定對(duì)象微猖。

(lldb) p userInfo
(__NSDictionaryM *) $0 = 0x0000000174242010 4 key/value pairs
(lldb) po userInfo
{
    macAddressString = "60:01:94:80:37:6c";
    payload =     {
        TimerAction = 0;
        TimerStat = 0;
        brightness = 70;
        colortemp = 93;
        remaining = "-1";
        switch = 1;
    };
    serialNumberString = 60019480376C;
    tcpPortString = "192.168.199.124";
}

兩個(gè)指令實(shí)際都是expression指令的縮寫谈息。p打印的是當(dāng)前對(duì)象的地址而po則會(huì)調(diào)用對(duì)象的description方法,做法和NSLog是一致的凛剥。

expression指令


expression命令是執(zhí)行一個(gè)表達(dá)式侠仇,并將表達(dá)式返回的結(jié)果輸出。包括上文提到的p指令在內(nèi)犁珠,以下幾個(gè)都是expression指令的別名逻炊。

(lldb)expression userInfo
(__NSDictionaryM *) $5 = 0x0000000174242010 4 key/value pairs 
(lldb) p userInfo
(__NSDictionaryM *) $2 = 0x0000000174242010 4 key/value pairs
(lldb) print userInfo
(__NSDictionaryM *) $3 = 0x0000000174242010 4 key/value pairs
(lldb) e userInfo
(__NSDictionaryM *) $4 = 0x0000000174242010 4 key/value pairs
(lldb) call userInfo
(__NSDictionaryM *) $5 = 0x0000000174242010 4 key/value pairs

打印對(duì)象的時(shí)候我們也可以指定特定格式,詳細(xì)的格式查閱參見(jiàn)這里盲憎。

(lldb) p 16
16
(lldb)p/x 16
0x10
(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000

但是expression指令真正強(qiáng)大的部分應(yīng)該是它的寫入能力嗅骄。我們可以通過(guò)expression來(lái)執(zhí)行一個(gè)表達(dá)式動(dòng)態(tài)的修改我們程序中變量的值胳挎。

(lldb) p count
(NSUInteger) $4 = 12
(lldb)e count = 42
(lldb) p count
(NSUInteger) $5 = 42

在斷點(diǎn)處我們首先打印count變量的值饼疙,之后通過(guò)執(zhí)行expression指令來(lái)修改count變量,再次打印可以發(fā)現(xiàn)此時(shí)count已經(jīng)被修改慕爬。這對(duì)于調(diào)試時(shí)模擬一些極端情況非常的有幫助窑眯。這里有一個(gè)特殊一點(diǎn)的情況需要指明,如果你嘗試通過(guò)expression來(lái)修改UI可能會(huì)失效医窿。

(lldb)expression -- self.view.backgroundColor = [UIColor redColor]

因?yàn)閳?zhí)行斷點(diǎn)會(huì)打斷更新UI的進(jìn)程導(dǎo)致你的修改沒(méi)有及時(shí)渲染出來(lái)磅甩,執(zhí)行flush命令可以讓機(jī)器渲染出你修改后的界面。

實(shí)際上一些復(fù)雜的調(diào)試操作單單靠每次命令行去手動(dòng)輸入指令是非常的繁瑣的姥卢,僅僅依靠單條指令和它提供的參數(shù)選項(xiàng)在一些針對(duì)界面的調(diào)試上并不能給予我們足夠多的支持卷要。令人興奮的是facebook開(kāi)源的Chisel為我們提供了更多實(shí)用的功能。整個(gè)開(kāi)源庫(kù)是用Python實(shí)現(xiàn)的独榴,基于LLDB 內(nèi)建的僧叉,完整的 Python 支持。這一部分我們后面聊到script指令再細(xì)細(xì)探討棺榔。

hook的概念


hook翻譯成中文是鉤子的意思瓶堕。這個(gè)名詞在我從事iOS開(kāi)發(fā)的過(guò)程中確實(shí)沒(méi)有太多的接觸,第一次碰到是在學(xué)習(xí)Flask框架時(shí)遇到的請(qǐng)求鉤子症歇。我并不覺(jué)得鉤子的中文翻譯對(duì)于我們理解有所幫助郎笆,在我初學(xué)的階段甚至給我產(chǎn)生了一定的誤解,所以我后續(xù)還是以hook來(lái)描述忘晤。

簡(jiǎn)單來(lái)說(shuō)hook一個(gè)處理消息的程序段宛蚓,通過(guò)系統(tǒng)調(diào)用,把它掛入系統(tǒng)设塔。每當(dāng)特定的消息發(fā)出苍息,在沒(méi)有到達(dá)目的窗口前,鉤子程序就先捕獲該消息,此時(shí)hook函數(shù)先得到控制權(quán)竞思。這時(shí)hook函數(shù)即可以加工處理(改變)該消息表谊,也可以不作處理而繼續(xù)傳遞該消息,還可以強(qiáng)制結(jié)束消息的傳遞盖喷。這意味著借助hook函數(shù)我們可以在指定在某些特殊的情況下做出一些包括但不限于參數(shù)驗(yàn)證爆办,消息攔截等操作來(lái)查驗(yàn)當(dāng)前情況和修改后續(xù)程序的運(yùn)行。

在LLDB中常見(jiàn)的操作有以下這些课梳。本身指令并不復(fù)雜距辆,但配合上其它的指令確實(shí)在某些情況下能節(jié)省我們很多的精力。

  • 設(shè)置一個(gè)stop-hook用以在每次斷點(diǎn)被觸發(fā)時(shí)執(zhí)行
(lldb)target stop-hook add --one-liner stop-hook
  • 設(shè)置一個(gè)stop-hook用以在指定函數(shù)func內(nèi)的斷點(diǎn)被觸發(fā)時(shí)執(zhí)行
(lldb) target stop-hook add --name func --one-liner stop-hook
  • 設(shè)置一個(gè)stop-hook用以在名為className的C類的斷點(diǎn)被觸發(fā)時(shí)執(zhí)行
(lldb)target stop-hook add -- className MyClass --one-liner stop-hook
stop-hook示例

流程控制


Xcode已經(jīng)為我們提供了可視化的工具暮刃,但是如果你習(xí)慣了命令行操作不希望雙手離開(kāi)鍵盤降低你的效率跨算,了解一下也是很有幫助的诸蚕。

流程控制
  • 繼續(xù)
(lldb)process continue
(lldb)continue
(lldb)c
  • 下一步
(lldb)thread step-over
(lldb)next
(lldb)n
  • 進(jìn)入
(lldb)thread step-in
(lldb)step
(lldb)s
  • 跳出
(lldb)thread step-out
(lldb) finish
(lldb)f

除此以外我們還可以通過(guò)Thread return來(lái)控制流程柱锹。該指令有一個(gè)可選參數(shù)烫沙,在執(zhí)行時(shí)它會(huì)把可選參數(shù)加載進(jìn)返回寄存器里瘸爽,然后立刻執(zhí)行返回命令,跳出當(dāng)前棧幀览露。這意味這函數(shù)剩余的部分不會(huì)被執(zhí)行偏化。當(dāng)然這也可能會(huì)給 ARC 的引用計(jì)數(shù)造成一些問(wèn)題只怎,或者會(huì)使函數(shù)內(nèi)的清理部分失效。但是在函數(shù)的開(kāi)頭執(zhí)行這個(gè)命令,是個(gè)非常好的隔離這個(gè)函數(shù),偽造返回值的方一個(gè)方法浓恶。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    if ([self isEvenNumber:2])
    {
        NSLog(@"First");
    }
    else
    {
        NSLog(@"Second");
    }
}

- (BOOL)isEvenNumber:(NSInteger)num
{
    if (num % 2 == 0)
    {
        return YES;
    }
    else
    {
        return NO;
    }
}

我們?cè)?code>isEvenNumber:函數(shù)中設(shè)置斷點(diǎn)利用thread return函數(shù)返回NO勉痴。

(lldb) thread return NO
(lldb) c
Process 4784 resuming
2017-07-22 18:48:14.654 LLDBTest[4784:569378] Second

script


LLDB 有內(nèi)建的,完整的 Python支持树肃。在LLDB中輸入 script蚀腿,會(huì)打開(kāi)一個(gè) Python REPL。你也可以輸入一行 python 語(yǔ)句作為 script 命令的參數(shù)扫外,這可以運(yùn)行 python 語(yǔ)句而不進(jìn)入REPL

(lldb) script print 'Hello World'
Hello World

借助LLDB提供的Python API我們可以實(shí)現(xiàn)很多復(fù)雜的功能莉钙。這里列舉一個(gè)簡(jiǎn)單的例子,將以下內(nèi)容寫入~/myCommands.py文件

def caflushCommand(debugger, command, result, internal_dict): 
    debugger.HandleCommand("e (void)[CATransaction flush]")

在LLDB中執(zhí)行

command script import ~/myCommands.py

或者將這條指令寫入~ /.lldbinit中筛谚,每次進(jìn)入LLDB都會(huì)自動(dòng)執(zhí)行這些函數(shù)磁玉。

如果沒(méi)有~ /.lldbinit 終端執(zhí)行touch ~ /.lldbinit生成文件
你可以在這里提前設(shè)置好一些指令,然后disable驾讲。調(diào)試過(guò)程中再設(shè)置enable打開(kāi)蚊伞。相信經(jīng)過(guò)整理之后LLDB會(huì)讓你的調(diào)試如魚得水。

Facebook開(kāi)源的Chisel就是基于此實(shí)現(xiàn)吮铭。我們通過(guò)brew安裝Chisel

brew install Chisel
chise文件層次

fblldbbase.py文件中定義了各個(gè)基礎(chǔ)類时迫,fblldb.py負(fù)責(zé)遍歷commands文件夾里的各個(gè)類來(lái)加載自定義的指令。在Chisel基礎(chǔ)上我們也可以輕松的自定義指令谓晌。在commands文件夾內(nèi)新建py文件掠拳,實(shí)現(xiàn)函數(shù)lldbcommands返回一個(gè)數(shù)組,包含對(duì)象的類都是FBCommand的子類纸肉。

def lldbcommands():
  return [
    FBPrintAccessibilityLabels()
  ]

class FBPrintAccessibilityLabels(fb.FBCommand):
  def name(self):
    return 'pa11y'

  def description(self):
    return 'Print accessibility labels of all views in hierarchy of <aView>'

  def args(self):
    return [ fb.FBCommandArgument(arg='aView', type='UIView*', help='The view to print the hierarchy of.', default='(id)[[UIApplication sharedApplication] keyWindow]') ]

  def run(self, arguments, options):
    forceStartAccessibilityServer();
    printAccessibilityHierarchy(arguments[0])

每一個(gè)類都繼承自FBCommand溺欧,我們需要分別復(fù)寫以下幾個(gè)函數(shù)

  • def name()
    返回一個(gè)字符串表示指令的名稱
  • def description()
    返回一個(gè)字符串表示指令的描述
  • def args()
    返回一個(gè)數(shù)組,其中的對(duì)象都是類FBCommandArgument構(gòu)建的柏肪,每一個(gè)對(duì)象表示一個(gè)指令的選項(xiàng)參數(shù)姐刁。
  • def run()
    指令的具體操作

image


image指令是target module指令的縮寫,借助它我們能夠查看當(dāng)前的Binary Images相關(guān)的信息烦味。日常開(kāi)發(fā)我們主要利用它尋址聂使。

在日常開(kāi)發(fā)的過(guò)程中,我們會(huì)收集到用戶各式各樣的crash log谬俄。log中會(huì)為我們提供崩潰前函數(shù)棧的運(yùn)行情況柏靶,每一個(gè)函數(shù)都會(huì)對(duì)應(yīng)一個(gè)函數(shù)地址。

crash log

要解決問(wèn)題首先我們需要確定的是程序最后調(diào)用了什么函數(shù)凤瘦。由于ALSR的原因crash log中的函數(shù)地址我們不能夠直接的去使用宿礁,我們需要在測(cè)試的機(jī)器上自己去計(jì)算出對(duì)應(yīng)的函數(shù)地址。一般情況下crash log中會(huì)附帶一個(gè)Binary Images蔬芥。我們要利用這個(gè)來(lái)計(jì)算出每一個(gè)函數(shù)地址相對(duì)于所在框架的偏移量梆靖。

Binary Images

之后利用image指令來(lái)查看本機(jī)的Binary Images控汉。

(lldb) image list
[  0] 48EA38EC-6E36-3E77-A680-A4D04D3D3868 0x00000001014ac000 /Users/noskthing/Library/Developer/Xcode/DerivedData/LLDBTest-dfmaxwkizubjskftkbnlfzumauje/Build/Products/Debug-iphonesimulator/LLDBTest.app/LLDBTest 
[  1] 322C06B7-8878-311D-888C-C8FD2CA96FF3 0x0000000107c66000 /usr/lib/dyld 
[  2] 14AD0238-D077-378B-82A8-AC2D2ADC9DDF 0x00000001014b4000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/dyld_sim 
[  3] 61CD1144-BB93-3571-BDB3-9F9B56CECFFE 0x0000000101543000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/Foundation.framework/Foundation 
[  4] 5F0E622C-86EC-3969-ACFB-CAAA10E21A31 0x0000000101a76000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//usr/lib/libobjc.A.dylib 

有了本機(jī)的Binary Images我們就可以通過(guò)之前計(jì)算出的偏移量來(lái)獲取本機(jī)對(duì)應(yīng)函數(shù)的地址。通過(guò)image lookup指令查找對(duì)應(yīng)地址的函數(shù)就可以確定崩潰前究竟執(zhí)行了哪些函數(shù)

(lldb) image lookup -a 0x1025dd00a
      Address: UIKit[0x00000000001cb00a] (UIKit.__TEXT.__text + 1869978)
      Summary: UIKit`-[UIViewController loadViewIfRequired] + 1219

register


register指令能夠獲取和修改各個(gè)寄存器的信息返吻。

我們需要明白一個(gè)典型的CPU是由運(yùn)算器姑子、控制器、寄存器等器件構(gòu)成的测僵,而寄存器進(jìn)行的就是信息存儲(chǔ)街佑。我們利用匯編語(yǔ)言來(lái)操作寄存器。

匯編

這里是蘋果官方文檔捍靠,介紹的是armv6沐旨。需要注意的是自從iPhone 5s之后已經(jīng)全部換到64-bit,在arm64下整數(shù)寄存器的個(gè)數(shù)已經(jīng)增加到31個(gè)榨婆。我們可以通過(guò)register read來(lái)進(jìn)行查看磁携。

register

其中x0-x7八個(gè)寄存器是用來(lái)保存參數(shù)的。objc_msgSend會(huì)有兩個(gè)默認(rèn)參數(shù)良风,這也就意味著x0保存的是self谊迄,x1保存的是_cmd。fr對(duì)應(yīng)frame point烟央,lr對(duì)應(yīng)link point统诺,在匯編中分別為x29,x30疑俭。最近正在準(zhǔn)備一篇從匯編的層面分析objc_msgSend的文章粮呢,會(huì)在那里結(jié)合官方文檔詳細(xì)介紹包括函數(shù)調(diào)用過(guò)程以及各個(gè)寄存器的作用。有興趣的朋友可以先關(guān)注一下作者:)

利用runtime動(dòng)態(tài)調(diào)用Objective-C任意對(duì)象的任意方法怠硼,需要為NSInvocation設(shè)置參數(shù)鬼贱。參數(shù)的index就是從2開(kāi)始的移怯。具體的實(shí)現(xiàn)可以參考Github-Tools中的NSObject+Runtime這個(gè)Category的實(shí)現(xiàn)香璃。

雖然我們更多的時(shí)候只是借助read指令來(lái)獲取一下當(dāng)前各個(gè)寄存器的信息,但是對(duì)于一些替換參數(shù)舟误,模擬特殊輸入的需求葡秒,write指令也是非常的有幫助。

實(shí)現(xiàn)一個(gè)簡(jiǎn)單的例子嵌溢。

- (void)viewDidLoad {
    [super viewDidLoad];

    NSString * str = [NSString stringWithFormat:@"First"];
    NSString * str1 = [NSString stringWithFormat:@"Second"];
    [self printString:str];
    [self printString:str1];
}

- (void)printString:(NSString *)str
{
    NSLog(@"%@",str);
}

函數(shù)非常的簡(jiǎn)單眯牧,會(huì)依次打印出First和Second。我們首先在第一次調(diào)用printString:之前打印一個(gè)斷點(diǎn)赖草,調(diào)用frame variable來(lái)查看一下當(dāng)前兩個(gè)參數(shù)的地址.

(lldb) frame variable
(ViewController *) self = 0x000000010090ac30
(SEL) _cmd = <variable not available>

(NSTaggedPointerString *) str = 0xa000074737269465 @"First"
(NSTaggedPointerString *) str1 = 0xa00646e6f6365536 @"Second"

如果你的參數(shù)是unused編譯器會(huì)把它優(yōu)化掉学少,這樣你就無(wú)法獲取它的地址。注意NSString的創(chuàng)建方式秧骑,字符串常量創(chuàng)建會(huì)把str分配到常量區(qū)版确,查看參數(shù)會(huì)得到<variable not available>的提示扣囊。

之后在printString:中的NSLog之前設(shè)置一個(gè)斷點(diǎn),continue绒疗。在printString:中遇到斷點(diǎn)的時(shí)候我們執(zhí)行register read指令侵歇。

(lldb) register read
General Purpose Registers:
        x0 = 0x000000010090ac30
        x1 = 0x000000010000995f  "printString:"
        x2 = 0xa000074737269465
        x3 = 0x000000016fdfd876
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000064
        x7 = 0x0000000000000000
        x8 = 0x00000001ae36bc20  libsystem_pthread.dylib`_thread + 224
        x9 = 0x00000001ae364fec  runtimeLock + 28
       x10 = 0x00000001ae364ff0  runtimeLock + 32
       x11 = 0x003c6d01003c6d80
       x12 = 0x0000000000000000
       x13 = 0x00000000003c6d00
       x14 = 0x00000000003c6e00
       x15 = 0x00000000003c6dc0
       x16 = 0x00000000003c6d01
       x17 = 0x0000000100007250  test`-[ViewController printString:] at ViewController.m:103
       x18 = 0x0000000000000000
       x19 = 0x000000010090ac30
       x20 = 0xa00646e6f6365536
       x21 = 0xa000074737269465
       x22 = 0x000000010000995f  "printString:"
       x23 = 0x0000000000000000
       x24 = 0x0000000000000010
       x25 = 0x0000000000000258
       x26 = 0x000000018ed0e90e  "window"
       x27 = 0x0000000000000001
       x28 = 0x0000000000000000
        fp = 0x000000016fdfddc0
        lr = 0x000000010000721c  test`-[ViewController viewDidLoad] + 156 at ViewController.m:100
        sp = 0x000000016fdfddb0
        pc = 0x000000010000725c  test`-[ViewController printString:] + 12 at ViewController.m:106
      cpsr = 0x60000000

對(duì)比地址可以發(fā)現(xiàn)x0保存的是viewController的地址,x1注明了是函數(shù)printString:的地址吓蘑,而x2就是str的地址惕虑。我們通過(guò)register write指令來(lái)修改x2的值。

(lldb) register write x2 0xa00646e6f6365536

contine之后你會(huì)發(fā)現(xiàn)打印出的不是First而是Second磨镶。

如果有朋友對(duì)匯編和函數(shù)調(diào)用感興趣溃蔫,我會(huì)在之后結(jié)合objc_msgSend匯編部分的代碼在另一篇文章里來(lái)做個(gè)介紹。

結(jié)語(yǔ)


文章的目的是希望給大家展示LLDB強(qiáng)大的能力以及命令行的優(yōu)點(diǎn)琳猫,但實(shí)際以上篇幅介紹的只是冰山一角酒唉。希望這篇文章能夠給大家一些幫助,來(lái)更多的了解LLDB沸移。

以下是一些有關(guān)LLDB的資料和文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痪伦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雹锣,更是在濱河造成了極大的恐慌网沾,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕊爵,死亡現(xiàn)場(chǎng)離奇詭異辉哥,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)攒射,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門醋旦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人会放,你說(shuō)我怎么就攤上這事饲齐。” “怎么了咧最?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵捂人,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我矢沿,道長(zhǎng)滥搭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任捣鲸,我火速辦了婚禮瑟匆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栽惶。我一直安慰自己愁溜,他們只是感情好无午,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著祝谚,像睡著了一般宪迟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上交惯,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天次泽,我揣著相機(jī)與錄音,去河邊找鬼席爽。 笑死意荤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的只锻。 我是一名探鬼主播玖像,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼齐饮!你這毒婦竟也來(lái)了捐寥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祖驱,失蹤者是張志新(化名)和其女友劉穎握恳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捺僻,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乡洼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匕坯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片束昵。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖葛峻,靈堂內(nèi)的尸體忽然破棺而出锹雏,到底是詐尸還是另有隱情,我是刑警寧澤泞歉,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布逼侦,位于F島的核電站,受9級(jí)特大地震影響腰耙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铲球,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一挺庞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稼病,春花似錦选侨、人聲如沸掖鱼。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戏挡。三九已至,卻和暖如春晨仑,著一層夾襖步出監(jiān)牢的瞬間褐墅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工洪己, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妥凳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓答捕,卻偏偏與公主長(zhǎng)得像逝钥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拱镐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容