LLDB

平常開發中咱們常用Xcode的斷點,這一強大的功能解決了咱們開發中99%的難題,可是咱們的斷點其實只是LLDB中的一小部分而已。express

一、什麼是 LLDB?

LLDB是英文Low Lever Debug的縮寫,是XCode內置的爲咱們開發者提供的調試工具,它與LLVM編譯器一塊兒,存在於主窗口底部的控制檯中,可以帶給咱們更豐富的流程控制和數據檢測的調試功能。數組

二、 LLDB 命令行斷點設置(正向開發)

新建一個工程,寫了下方代碼,給一個 OC 方法斷點,這是咱們日常使用 Xcode 界面下的斷點,那如今咱們嘗試使用控制檯 LLDB 下。sass

斷點

一、給函數下斷點

LLDB 輸入 breakpoint set -n test1 後回車發現控制檯打印了一些東西。告訴咱們了一些東西:安全

  • Breakpoint 2 :這個斷點是第二個斷點
  • where LLDB調試 test1 + 11 at ViewController.m:23:5 :告訴了咱們斷點的位置
  • address = 0x000000010fb07ecb:斷點的地址
(lldb) breakpoint set -n test1
Breakpoint 2: where = LLDB調試`test1 + 11 at ViewController.m:23:5, address = 0x000000010fb07ecb
(lldb) 
複製代碼

二、給方法下斷點

上個是給函數下斷點,咱們如今給OC方法下斷點。在界面上建立三個 UIButton ,而後添加點擊方法。bash

3個按鈕

LLDB 輸入 : breakpoint set -n "[ViewController onWeChatClicked:]" -n "[ViewController onQQClicked:]" -n "[ViewController onSinaClicked:]"框架

回車,發現控制檯打印了 Breakpoint 1: 3 locations ,告訴咱們:斷點1:在3個位置添加了dom

那咱們再在LLDB上輸入breakpoint list 就能顯示咱們下的斷點的詳細信息,而後輸入c 就能夠過掉斷點了,點擊按鈕嘗試一下發現和界面的功能是同樣的。函數

(lldb) breakpoint set -n "[ViewController onWeChatClicked:]" -n "[ViewController onQQClicked:]" -n "[ViewController onSinaClicked:]"
Breakpoint 1: 3 locations.
(lldb) breakpoint list
Current breakpoints:
1: names = {'[ViewController onWeChatClicked:]', '[ViewController onWeChatClicked:]', '[ViewController onQQClicked:]', '[ViewController onQQClicked:]', '[ViewController onSinaClicked:]', '[ViewController onSinaClicked:]'}, locations = 3, resolved = 3, hit count = 0
  1.1: where = LLDB調試`-[ViewController onWeChatClicked:] + 43 at ViewController.m:22:5, address = 0x0000000102ff0dab, resolved, hit count = 0 
  1.2: where = LLDB調試`-[ViewController onQQClicked:] + 43 at ViewController.m:27:5, address = 0x0000000102ff0dfb, resolved, hit count = 0 
  1.3: where = LLDB調試`-[ViewController onSinaClicked:] + 43 at ViewController.m:31:5, address = 0x0000000102ff0e4b, resolved, hit count = 0 

(lldb) c
複製代碼

斷點結果

三、禁用斷點

由於咱們如今是經過LLDB直接下的斷點,界面上已經不能對斷點進行操做了,好比:禁用斷點,刪除斷點等等。如今咱們就須要用LLDB的指令去完成這些事情了。工具

LLDB輸入:breakpoint disable 1,表明的是禁用第一組所有斷點,再通過測試,發現咱們剛纔3個點擊事件的斷點所有失效了。佈局

禁用斷點

斷點未生效。

禁用斷點測試

四、啓用斷點

而後咱們再次啓用斷點,LLDB輸入:breakpoint enable 1,就啓用了第一組斷點。

(lldb) breakpoint enable 1
1 breakpoints enabled.
(lldb) breakpoint list
Current breakpoints:
1: names = {'[ViewController onWeChatClicked:]', '[ViewController onWeChatClicked:]', '[ViewController onQQClicked:]', '[ViewController onQQClicked:]', '[ViewController onSinaClicked:]', '[ViewController onSinaClicked:]'}, locations = 3, resolved = 3, hit count = 3
  1.1: where = LLDB調試`-[ViewController onWeChatClicked:] + 43 at ViewController.m:22:5, address = 0x0000000102ff0dab, resolved, hit count = 1 
  1.2: where = LLDB調試`-[ViewController onQQClicked:] + 43 at ViewController.m:27:5, address = 0x0000000102ff0dfb, resolved, hit count = 1 
  1.3: where = LLDB調試`-[ViewController onSinaClicked:] + 43 at ViewController.m:31:5, address = 0x0000000102ff0e4b, resolved, hit count = 1 

(lldb) 
複製代碼

五、禁用單個斷點

第一組中有3個斷點,如今想只禁用其中一個,LLDB輸入:breakpoint disable 1.1,回車後發現,第一組 1.1 的斷點處於 disabled 狀態。

禁用1組中1個斷點

六、界面斷點 LLDB 查看

在界面上下的斷點一樣也能在LLDB上使用 breakpoint list 看到。

界面斷點

七、刪除斷點

添加了斷點,固然還須要能刪除斷點。

LLDB輸入:breakpoint delete 1.1 ,回車,可是控制檯卻打印的是0 breakpoints deleted; 1 breakpoint locations disabled.

LLDB 輸入breakpoint list 查看,發現 1.1 是禁用狀態,這是由於想要刪除只能刪除一組斷點,不能刪除一組中的一個,就算對一組中的一個斷點輸入了刪除指令,LLDB只會將這個斷點禁用。

既然只能刪除一組,那咱們在LLDB輸入:breakpoint delete 1,回車,打印了 1 breakpoints deleted; 0 breakpoint locations disabled., 1個斷點被刪除,0個斷點被禁用。

刪除斷點

LLDB輸入:breakpoint delete 刪除全部斷點。

(lldb) breakpoint delete
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (2 breakpoints)
(lldb) 
複製代碼

八、查看 LLDB 的其餘指令

LLDB 上 輸入 :help 就能夠查看其餘的指令了。

help

好比:help breakpoint,就是查看 breakpoint下的全部指令。

help breakpoint

九、給某一個方法設置斷點

一、給工程內全部 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event; 設置斷點。

LLDB 上 輸入 : breakpoint set --selector touchesBegan:withEvent:,回車控制檯打印了 Breakpoint 4: 94 locations. 第4組斷點,一共設置了94處。

點擊屏幕後,很顯然這個不是剛纔工程內的方法,這是由於UIKit框架中,基本上全部的可操做的控件都會有手勢,因此當前斷點進入系統的方法了。

給一個方法設置斷點

二、如今須要給特定文件裏的一個方法設置斷點,好比 ViewController.m 裏的 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

咱們先刪除全部的斷點,而後在 LLDB 上 輸入 :breakpoint set --file ViewController.m --selector touchesBegan:withEvent:,回車就顯示咱們這個斷點設置上了。

(lldb) breakpoint set  --file ViewController.m --selector  touchesBegan:withEvent:
Breakpoint 8: where = LLDB調試`-[ViewController touchesBegan:withEvent:] + 77 at ViewController.m:40:5, address = 0x0000000102ff0edd
(lldb) 
複製代碼

十、給包含某一個字符串的全部方法設置斷點

如今須要給包含 Clicked: 的方法下斷點。

LLDB 輸入:breakpoint set -r Clicked:,回車,發現有25個地方下了斷點了。

breakpoint list 查看一下,看到了除了咱們當前 ViewController.m 文件中包含 Clicked: 的方法被下了斷點,一些系統方法也被下了斷點。

設置包含字符串的斷點

固然咱們也能夠指定文件去下斷點。好比:breakpoint set --file ViewController.m -r Clicked:

設置一個文件包含字符串的斷點

十一、斷點設置的簡寫

b -f ViewController.m -r Clicked: 等同於:breakpoint set --file ViewController.m -r Clicked:

  • bbreakpoint set
  • -f--file
  • -n--name

可是 breakpoint listbreakpoint disable 等只能簡寫成 break libreak dis ,由於簡寫的 b 後面默認會帶上 set 的。

三、 LLDB 一些指令的含義和進階使用方法

咱們常常 Xcode 調試的時候在 LLDB 上輸入:p xxx 或者 輸入了po xxx,就獲取了一個對象的值。那麼 p 或者 po 含義究竟是什麼呢?

LLDB輸入help phelp po,查看說明以下:

  • pexpression 的簡寫。LLDB上輸入的 p xxx執行 xxx
  • poexpression -O的縮寫。再輸入 help expressionpo : expression --object-descriptionLLDB上輸入的 po xxx執行 xxx 的 description 方法

help p

既然 p 是執行一個方法,那麼 LLDB 輸入:p self.view.backgroundColor = [UIColor redColor]; ,回車,過掉斷點 self.view 就變成紅色的了。

紅色背景

固然也能夠是使用 p 指令作更多操做。新建一個Person類以下:

//
//  Person.h
//  LLDB調試
//
//  Created by ABC on 2019/10/27.
//  Copyright © 2019 ABC. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic,strong) NSString *name;

@property (nonatomic,assign) int age;

@end

NS_ASSUME_NONNULL_END
複製代碼

而後ViewController上有個 @property (nonatomic,strong) NSMutableArray *dataArray;,接下來操做以下方所示(這裏就不用圖片了,方便複製):

(lldb) b -f ViewController.m -r touch
Breakpoint 1: where = LLDB調試`-[ViewController touchesBegan:withEvent:] + 77 at ViewController.m:43:5, address = 0x0000000106421c0d
(lldb) c
Process 2221 resuming
(lldb) po self
<ViewController: 0x7fb2d5d07c20>

(lldb) po self.dataArray
<__NSArrayM 0x600003705440>(

)

(lldb) p [self.dataArray addObject:[Person new]];
(lldb) po self.dataArray
<__NSArrayM 0x600003705440>(
<Person: 0x60000395b820>
)

(lldb) p self.dataArray.lastObject;
(Person *) $3 = 0x000060000395b820
(lldb) p [(Person *)$3 setValue:@"張三" forKey:@"name"];
(lldb) p (Person *)self.dataArray.lastObject
(Person *) $4 = 0x000060000395b820
(lldb) p $4.name;
(__NSCFString *) $5 = 0x0000600003972d40 @"張三"
(lldb) p $4.name = @"李四";
(__NSCFString *) $6 = 0x0000600003972500 @"李四"
(lldb) p $4.name;
(__NSCFString *) $7 = 0x0000600003972500 @"李四"
(lldb) p Person *person = [Person new];person.name = @"王五";person.age = 12;[self.dataArray addObject:person];
(lldb) p self.dataArray
(__NSArrayM *) $8 = 0x0000600003705440 @"2 elements"
(lldb)  p (Person *)self.dataArray.lastObject
(Person *) $9 = 0x0000600003972dc0
(lldb) p $9.name;
(__NSCFString *) $10 = 0x0000600003972da0 @"王五"
(lldb) p $9.age;
(int) $11 = 12
(lldb) 
複製代碼

上方能夠看到,咱們可使用LLDB 給一個數組裏面添加對象,也可使用 LLDB 生成一個對象,而後給對象屬性賦值,最後再添加到數組裏面。不得不說 LLDB的強大。

注意:(Person *)self.dataArray.lastObject 只要拿值的時候給上對象的類加上它的類型,好比:(Person *),咱們就能夠直接使用返回的 $4 對象取其屬性的值 $4.name了。

接下來查看函數調用棧。在 ViewController.m 中添加下方代碼(有相同的替換一下)。

- (void)lldbText1 {
    [self lldbText2];
}

- (void)lldbText2 {
    [self lldbText3];
}

- (void)lldbText3 {
    [self lldbText4];
}

- (void)lldbText4 {
    NSLog(@"%s",__func__);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"點擊了界面111");
    NSLog(@"點擊了界面222");
    [self lldbText1];
}
複製代碼

控制檯給 - (void)lldbText3; 方法設置斷點:b -f ViewController.m -r lldbText3,設置好了,點擊屏幕就進入了 - (void)lldbText3; 方法,想要查看調用隊棧,LLDB 輸入 bt,顯示以下:

調用隊棧查看

想要回到上一個方法 LLDB 輸入 up,去下一個 down

堆棧跳轉

既然使用使用了 bt 查看了隊棧,那也可使用 frame select 1frame select 3 直接跳轉相應的隊棧。

frame select

若是想查看 lldbText2 中的參數可使用 frame variable,這也必定程度告訴了咱們,方法中的 self 不必定是咱們當前的 ViewController

image.png

更改一下上方代碼,將 lldbText1lldbText2lldbText3lldbText4 都添加上參數 str,而且給 lldbText3 設置一個斷點。

代碼修改

運行後點擊屏幕,進入斷點 lldbText3,若是咱們使用 up 指令進入了 lldbText2 而後修改了 strlldbText4 輸出的結果會更改嗎?

結果是不會的,由於咱們 lldbText2 方法已經走過了,至關於過去的時間咱們沒法修改同樣。

修改走過的隊棧參數

可是必定要修改呢?

咱們可使用 thread return ,可是這個方法執行事後進入了 lldbText2,修改了 lldbText2 中的 str 後過掉斷點,並無打印出 lldbText4,其實 thread return 執行後,給當前的方法後面添加了一個 return,因此就不會往下繼續執行了。

thread return

還有一些流程控制指令

  • $continue c
  • 單步運行,將子函數當作總體一步執行 $n next,彙編之下使用 ni
  • 單步運行,遇到子函數會進去 $s,彙編之下使用 si

其餘指令

  • image list
  • p
  • b -[xxx xxx]
  • x
  • register read
  • po
  • stop-hook 讓你在每次stop(斷點)的時候去執行一些命令,只針對 breadpoint,watchpoint

四、 LLDB 命令行斷點設置(逆向開發)

上述的指令在逆向開發中基本沒有什麼用~~~~,感受有點小崩潰。

咱們正向開發的時候,是有 符號文件表 的,全部的方法 Xcode 都會幫咱們解析,可是上傳到 AppStore 的應用是沒有符號文件的。好比咱們使用 Crash 收集工具的時候(Bugly友盟 或者 Xcode),都會讓咱們上傳符號文件表,否則就沒法解析,這也是對應用的一種保護措施。

沒有符號表咱們只能打印出一堆看不懂的東西,也就沒法對其餘App進行下一步調試,也沒法使用函數名稱去下斷點, 因此上述的指令在逆向開發中基本沒有什麼用。

既然正向有這麼多的手段可使用,固然逆向也有,在逆向工程中,咱們能夠打 內存斷點

一、內存斷點的使用

修改 viewDidLoad 代碼以下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *p1 = [Person new];
    p1.name = @"111";
    p1.age = 1;
    
    Person *p2 = [Person new];
    p2.name = @"222";
    p2.age = 2;
    
    Person *p3 = [Person new];
    p3.name = @"333";
    p3.age = 3;
    
    [self.dataArray addObject:p1];
    [self.dataArray addObject:p2];
    [self.dataArray addObject:p3];
}
複製代碼

修改 touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 代碼以下:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    Person *p1 = [self.dataArray firstObject];
    p1.name = @"ABC";
}
複製代碼

[super viewDidLoad]; 這行添加斷點,從新運行,而後使用 n 或者 Xcode 工具讓 LLDB 走到 p1 建立完畢。

p1建立完畢

p1 建立完畢後,p1 的對象已經存在堆區,指向 p1 對象的指針由於 [self.dataArray addObject:p1]; 方法,在 viewDidLoad 方法走完以後依舊被保存。

如今須要給 p1name 屬性下斷點,咱們可使用 watchpoint set variable p1->_namename 下內存斷點。回車後 LLDB 清楚的告訴了咱們:內存斷點的地址、指針佔用的大小(OC對象8字節)。

name的內存斷點

過掉斷點,點擊屏幕觸發 touchesBeganp1.name = @"ABC" 賦值方法,這樣一個內存斷點就觸發了, po 上方打印的兩個地址,咱們就能看舊值和新值。而後輸入 bt 查看調用堆棧,就能清楚的看到調用隊棧。

觸發內存斷點

隊棧信息查看

咱們也可使用 p1->_name 內存的指針地址給 p1->_name 下內存斷點。

  • 獲取 p1->_name 的內存地址:&p1->_name
  • 下內存斷點:使用watchpoint set expression 加上p1->_name 的內存地址
  • 點擊屏幕觸發賦值方法驗證

指針地址下內存斷點

同符號斷點同樣,內存斷點能夠查看和刪除

  • 查看內存斷點:watchpoint list
  • 刪除內存斷點:watchpoint delete

image.png

二、給一個斷點添加多指令

修改 touchesBegan:withEvent: 中的代碼以下。

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"點擊了界面111");
    NSLog(@"點擊了界面222");
    [self lldbText1:@"123"];
}
複製代碼

而後給 lldbText1: 添加一個符號斷點 b -[ViewController lldbText1:]

lldbText1 符號斷點

LLDB 上輸入 breakpoint list ,而後給斷點1添加多個指令 breakpoint command add 1, 而後在 > 後輸入指令,每輸入完成一個指令後回車進行下一個指令輸入,想要結束,輸入DONE便可。

指令集

文字版方便複製

(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> po self
> p self.view.backgroundColor
> p self.view.backgroundColor = [UIColor redColor];
> DONE
複製代碼

過掉斷點驗證一下,進入斷點的同時執行了剛纔添加的指令,再過掉斷點,看到屏幕變紅色了。

image.png

刪除斷點1的指令集可使用 breakpoint command delete 1 ,查指令集列表 : breakpoint command list 發現已經沒有了。

三、給全部斷點添加多指令

上一條是給某一個斷點添加多個指令,那咱們逆向的時候常常須要使用一些指令,咱們若是給全部斷點添加多指令,只要有斷點來了,就執行咱們的指令,這樣就方便太多了。

好比咱們常使用的指令: frame variable (查看一個方法下的全部參數)。就能夠經過 target stop-hook add -o "frame variable" 給全部斷點添加這樣一個指令。

  • target stop-hook 目標是斷點
  • add 添加
  • -o--one 的意思,添加一條指令
  • "frame variable" 須要添加的指令

回車,過掉斷點,給touchesBegan:withEvent: 添加一個斷點, 點擊屏幕後進入斷點後執行了指令 frame variable

stop-hook

一些其餘指令

  • target stop-hook list 查看列表
  • target stop-hook delete 1 刪除第一組斷點
  • target stop-hook delete 刪除全部斷點
  • target stop-hook disable 1 禁用第一組斷點
  • undisplay 1 刪除第一組斷點,和 target stop-hook delete 1 功能同樣。

四、給全部斷點使用腳本添加多指令

每次Xcode 運行時, LLDB 啓動就會加載一個文件 .lldbinit,在~目錄下輸入ls -a(列出全部文件,包括隱藏文件,. 開頭的就是隱藏文件),就能找到(若是沒有本身建立一個便可),vi .lldbinit,而後點擊 i 添加咱們剛纔的指令 target stop-hook add -o "frame variable",鍵盤按 ESC 輸入:wq 保存並退出。

由於是給系統的 LLDB 添加的,因此對每一個工程都會有效。

而後回到咱們的 Xcode 隨意下一個斷點。

腳本斷點

四、image指令

  • image list :查看全部加載的庫
  • image lookup -t 類名 查看一個類的頭文件信息

五、方法斷點

逆向中沒有符號文件,那咱們怎麼給方法下斷點呢?

這時候咱們就須要藉助一個工具 Hopper Disassembler 了,將咱們工程的LLDB.MachO文件拖入Hopper Disassembler

image.png

Hopper Disassembler

須要給 ViewControllerlldbText1: 的方法添加一個斷點。該方法的的內存地址爲0x100001980

image.png

嘗試下內存斷點 b -a 0x100001980,回車 LLDB 告訴咱們 : warning: failed to set breakpoint site at 0x100001980 for breakpoint 2.1: error: 0 sending the breakpoint request

當前斷點並無下成功,那這是爲何呢?

由於咱們的方法的內存地址是相對於 MachO 文件在內存中的地址計算的。

lldbText1: 在文件中的偏移實際上是 0x1980 ,須要正確的下到方法的內存斷點上,就須要 MachO 文件在內存中的地址。

當前 Xcode 工程的 LLD 中輸入 image list,第一行就是 MachO 在內存中的地址,而後用這個地址加上 lldbText1: 的偏移量就是真實運行時 lldbText1 內存的地址。

當前運行的工程中的 MachO 的 地址爲 0x102f10000(每次運行都會變),lldbText1: 的偏移量 0x1980 ,二者相加 LLDB 輸入 b -a 0x102F11980 ,回車,過掉斷點,點擊屏幕測試,斷點到了 lldbText1:

MachO地址

方法斷點測試

ASLR

在計算機科學中,地址空間配置隨機加載(英語:Address space layout randomization,縮寫 ASLR ,又稱 地址空間配置隨機化地址空間佈局隨機化)是一種防範內存損壞漏洞被利用的計算機安全技術。

咱們知道 :物理地址 = ASLR + 方法虛擬地址

因此咱們當前運行的 MachO 內存地址爲 0x102f10000,因此 ASLR0x2f10000,而後咱們直接加上lldbText1: 的函數地址 0x100001980(咱們剛纔 Hopper Disassembler 獲取到的虛擬地址) ,因此 LLDB 上輸入b -a 0x2f10000+0x100001980 回車,測試,咱們也能斷到這個 lldbText1: 的方法。

測試結果

以上就是咱們 LLDB 在正向和逆向中的不一樣使用方法,有問題歡迎指出。

相關文章
相關標籤/搜索