平常開發中咱們常用Xcode
的斷點,這一強大的功能解決了咱們開發中99%
的難題,可是咱們的斷點其實只是LLDB
中的一小部分而已。express
LLDB
是英文Low Lever Debug
的縮寫,是XCode
內置的爲咱們開發者提供的調試工具,它與LLVM編譯器一塊兒,存在於主窗口底部的控制檯中,可以帶給咱們更豐富的流程控制和數據檢測的調試功能。數組
新建一個工程,寫了下方代碼,給一個 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
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
狀態。
在界面上下的斷點一樣也能在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
上 輸入 :help
就能夠查看其餘的指令了。
好比:help breakpoint
,就是查看 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:
b
:breakpoint set
-f
:--file
-n
:--name
可是 breakpoint list
、breakpoint disable
等只能簡寫成 break li
、break dis
,由於簡寫的 b
後面默認會帶上 set
的。
咱們常常 Xcode
調試的時候在 LLDB
上輸入:p xxx
或者 輸入了po xxx
,就獲取了一個對象的值。那麼 p
或者 po
含義究竟是什麼呢?
LLDB
輸入help p
和 help po
,查看說明以下:
p
是 expression
的簡寫。LLDB
上輸入的 p xxx
爲 執行 xxxpo
是 expression -O
的縮寫。再輸入 help expression
,po
: expression --object-description
。LLDB
上輸入的 po xxx
爲 執行 xxx 的 description 方法既然 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 1
、frame select 3
直接跳轉相應的隊棧。
若是想查看 lldbText2
中的參數可使用 frame variable
,這也必定程度告訴了咱們,方法中的 self
不必定是咱們當前的 ViewController
。
更改一下上方代碼,將 lldbText1
、lldbText2
、lldbText3
、lldbText4
都添加上參數 str
,而且給 lldbText3
設置一個斷點。
運行後點擊屏幕,進入斷點 lldbText3
,若是咱們使用 up
指令進入了 lldbText2
而後修改了 str
,lldbText4
輸出的結果會更改嗎?
結果是不會的,由於咱們 lldbText2
方法已經走過了,至關於過去的時間咱們沒法修改同樣。
可是必定要修改呢?
咱們可使用 thread return
,可是這個方法執行事後進入了 lldbText2
,修改了 lldbText2
中的 str
後過掉斷點,並無打印出 lldbText4
,其實 thread return
執行後,給當前的方法後面添加了一個 return
,因此就不會往下繼續執行了。
還有一些流程控制指令
$continue c
$n next
,彙編之下使用 ni
$s
,彙編之下使用 si
其餘指令
image list
p
b -[xxx xxx]
x
register read
po
stop-hook
讓你在每次stop
(斷點)的時候去執行一些命令,只針對 breadpoint
,watchpoint
上述的指令在逆向開發中基本沒有什麼用~~~~,感受有點小崩潰。
咱們正向開發的時候,是有 符號文件表 的,全部的方法 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
對象的指針由於 [self.dataArray addObject:p1];
方法,在 viewDidLoad
方法走完以後依舊被保存。
如今須要給 p1
的 name
屬性下斷點,咱們可使用 watchpoint set variable p1->_name
給 name
下內存斷點。回車後 LLDB
清楚的告訴了咱們:內存斷點的地址、指針佔用的大小(OC
對象8字節)。
過掉斷點,點擊屏幕觸發 touchesBegan
的 p1.name = @"ABC"
賦值方法,這樣一個內存斷點就觸發了, po
上方打印的兩個地址,咱們就能看舊值和新值。而後輸入 bt
查看調用堆棧,就能清楚的看到調用隊棧。
咱們也可使用 p1->_name
內存的指針地址給 p1->_name
下內存斷點。
p1->_name
的內存地址:&p1->_name
watchpoint set expression
加上p1->_name
的內存地址同符號斷點同樣,內存斷點能夠查看和刪除
watchpoint list
watchpoint delete
修改 touchesBegan:withEvent:
中的代碼以下。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"點擊了界面111");
NSLog(@"點擊了界面222");
[self lldbText1:@"123"];
}
複製代碼
而後給 lldbText1:
添加一個符號斷點 b -[ViewController 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
複製代碼
過掉斷點驗證一下,進入斷點的同時執行了剛纔添加的指令,再過掉斷點,看到屏幕變紅色了。
刪除斷點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
。
一些其餘指令
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 list
:查看全部加載的庫image lookup -t 類名
查看一個類的頭文件信息逆向中沒有符號文件,那咱們怎麼給方法下斷點呢?
這時候咱們就須要藉助一個工具 Hopper Disassembler 了,將咱們工程的LLDB.MachO
文件拖入Hopper Disassembler
。
須要給 ViewController
的 lldbText1:
的方法添加一個斷點。該方法的的內存地址爲0x100001980
。
嘗試下內存斷點 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:
。
在計算機科學中,地址空間配置隨機加載(英語:Address space layout randomization,縮寫 ASLR
,又稱 地址空間配置隨機化 、地址空間佈局隨機化)是一種防範內存損壞漏洞被利用的計算機安全技術。
咱們知道 :物理地址 = ASLR + 方法虛擬地址
因此咱們當前運行的 MachO
內存地址爲 0x102f10000
,因此 ASLR 爲 0x2f10000
,而後咱們直接加上lldbText1:
的函數地址 0x100001980
(咱們剛纔 Hopper Disassembler
獲取到的虛擬地址) ,因此 LLDB
上輸入b -a 0x2f10000+0x100001980
回車,測試,咱們也能斷到這個 lldbText1:
的方法。
以上就是咱們 LLDB
在正向和逆向中的不一樣使用方法,有問題歡迎指出。