本文主要理解OC對(duì)象反匯編浩姥,以及block常見類型的反匯編
OC反匯編
創(chuàng)建一個(gè)Person類揭蜒,并在main函數(shù)中初始化一個(gè)Person對(duì)象
@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) int age;
+ (instancetype)person;
@end
@implementation Person
+ (instancetype)person{
return [[self alloc] init];
}
@end
<!--main.m中-->
int main(int argc, char * argv[]) {
Person *p = [Person person];
return 0;
}
-
運(yùn)行洪囤,查看其匯編代碼
1毙石、靜態(tài)調(diào)試
通過adrp+add獲取地址救赐,分別讀取x0谴古,x1
- 讀取x0撞羽,讀出來是Person:
x 0x100c68eb0
+po 0x0100c68f98
- 讀取x1掏缎,讀取出來是person方法:
x 0x100c68e88
+p (SEL)0x01c019aef5
2皱蹦、動(dòng)態(tài)調(diào)試
通過一步一步執(zhí)行匯編,來驗(yàn)證x0御毅、x1是否如靜態(tài)調(diào)試的結(jié)果一致根欧?
通過調(diào)試發(fā)現(xiàn),是一致的端蛆,其實(shí)這里的
x0凤粗、x1
就是 objc_msgSend
的隱藏參數(shù)(id self,SEL _cmd)
下面我們繼續(xù)調(diào)試匯編
-
點(diǎn)擊step into今豆,直接進(jìn)入
[Person person]
方法(注意:這里不同iOS版本嫌拣,多看到的匯編代碼是有所區(qū)別的)-
從這里看到ios13.4系統(tǒng)的alloc、init并不會(huì)走objc_msgSend
- ios11版本中呆躲,可以看到objc_msgSend异逐,其本質(zhì)是在調(diào)用init方法
動(dòng)態(tài)調(diào)試進(jìn)行驗(yàn)證,結(jié)果如下所示插掂,是一致的
查看此時(shí)的x0灰瞻,已經(jīng)是一個(gè)實(shí)例對(duì)象腥例,因?yàn)閍lloc開辟了內(nèi)存,已經(jīng)分配了空間酝润,具體的內(nèi)部實(shí)現(xiàn)可以查看這篇文章iOS-底層原理 02:alloc & init & new 源碼分析
疑問:為什么版本不同燎竖,調(diào)用不一樣呢? - 在不同的版本下要销,系統(tǒng)在運(yùn)行時(shí)是不一樣的构回。因?yàn)橄到y(tǒng)對(duì)alloc 、init進(jìn)行了優(yōu)化
-
-
接著往下看疏咐,點(diǎn)擊step out 跳出[Person person],此時(shí)返回值在x0中
-
執(zhí)行到
bl ... objc_storeStrong
,objc_storeStrong是OC中用strong修飾的對(duì)象底層都是調(diào)用這個(gè)函數(shù)纤掸,詳情可以看這篇文章iOS-底層原理 10:strong©&weak底層分析 以及 方法簽名和attribute簡(jiǎn)寫含義。
疑問:我們此時(shí)并沒有使用strong修飾浑塞?:此時(shí)的局部變量p在此時(shí)就相當(dāng)于一個(gè)強(qiáng)引用借跪,是默認(rèn)的。且這個(gè)方法執(zhí)行完成后缩举,相當(dāng)于銷毀p
查看此時(shí)的x0垦梆、x1
,相當(dāng)于objc_storeStrong(&p仅孩,nil)
托猩,將nil進(jìn)行retain,將nil等于p(即 p=nil)辽慕,p進(jìn)行釋放
查看
objc_storeStrong
源碼-
目的:對(duì)一個(gè)strong修飾的對(duì)象進(jìn)行retain +1京腥,對(duì)一個(gè)老的對(duì)象進(jìn)行release
- 為什么是指針? 因?yàn)楹瘮?shù)是值傳遞溅蛉,而函數(shù)內(nèi)部需要修改p的值
/*
- id *location 指向?qū)ο蟮闹羔? 本質(zhì)上是 &p(即局部變量地址)
- id obj 對(duì)象
目的:對(duì)一個(gè)strong修飾的對(duì)象進(jìn)行retain +1公浪,對(duì)一個(gè)老的對(duì)象進(jìn)行release
為什么是指針? 因?yàn)楹瘮?shù)是值傳遞船侧,而函數(shù)內(nèi)部需要修改p的值
*/
void
objc_storeStrong(id *location, id obj)
{
//prev 相當(dāng)于p 欠气,因?yàn)閘ocation是 &p
id prev = *location;
//第二個(gè)參數(shù) == 第一個(gè)參數(shù),直接return
if (obj == prev) {
return;
}
//retain+1
objc_retain(obj);
//修改p的值镜撩,指向第二個(gè)對(duì)象
*location = obj;
//釋放老對(duì)象
objc_release(prev);
}
相當(dāng)于
Person *p = p1;
p = p2;//此時(shí)p1釋放预柒,p2retain+1
所以以上匯編中的objc_storeStrong(&p,nil)
的實(shí)現(xiàn)代碼如下
objc_storeStrong(&p袁梗,nil){
id prev = p;
if nil == p{
return;
}
objc_retain(nil);
p = nil;//指針指向nil
objc_release(p);//釋放堆空間
}
- 下面來進(jìn)行動(dòng)態(tài)驗(yàn)證宜鸯,發(fā)現(xiàn)
Person對(duì)象指向nil
[[self alloc] init] 優(yōu)化過程
- 在最初的版本(iOS9)中,相當(dāng)于兩次消息發(fā)送
objc_msgSend
- iOS11版本 是一次消息發(fā)送
objc_alloc + objc_msgSend
- iOS13.5.1以上版本遮怜,已經(jīng)沒有objc_msgSend淋袖,而是
objc_alloc_init
以上是LLDB動(dòng)態(tài)調(diào)試Person *p = [Person person]; //objc_msgSend x0,x1
通過工具看復(fù)雜的OC代碼
在上述OC代碼的基礎(chǔ)上增加一些代碼锯梁,然后再來靜態(tài)分析
int main(int argc, char * argv[]) {
Person *p = [Person person]; //objc_msgSend x0即碗,x1
p.name = @"CJL";
p.age = 18;
return 0;
}
CMD + B 編譯程序焰情,生成mach-o文件,并找到該文件
-
通過Hopper反匯編mach-o文件剥懒,main函數(shù)的分析如下
-
雙擊
objc_cls_ref_Person
烙样,查看p的地址,是000000010000ce88
蕊肥,是在Data段
通過MachOView
打開mach-o分析,查找000000010000ce88
蛤肌,與Hopper中的顯示是一致的
-
雙擊
@selector(person)
壁却,查看person方法的反匯編
雙擊0x10000cc68
雙擊“person”
,地址為 0x10000752a
在mach中查找0x10000752a
裸准,所有方法的name都在CString
中
Block反匯編
定義一個(gè)block
int main(int argc, char * argv[]) {
void(^block)(void) = ^(){
NSLog(@"block");
};
block();
return 0;
}
反匯編分析block的目的是想快速定位block的invoke
展东,因?yàn)閕nvoke中是實(shí)現(xiàn)代碼,以下是block的匯編代碼
-
查看
x0
是什么炒俱?:是一個(gè)__block_literal_global
盐肃,是一個(gè)全局靜態(tài)block
(即block不引用block外部變量
,在編譯時(shí)期
就可以確定內(nèi)存
的分配等操作权悟,存在于可執(zhí)行文件的常量區(qū)
)砸王,其他詳情也可查看iOS-底層原理 30:Block底層原理這篇文章
以下是源碼中block的定義,是一個(gè)結(jié)構(gòu)體
struct Block_layout{
void *isa;
volatile int32_t flags; //contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
//imported variables
};
然后動(dòng)態(tài)調(diào)試查看block的內(nèi)存結(jié)構(gòu)
-
是否可以通過hopper查看 adrp + add 是一個(gè)block峦阁?
答案是可以的
- 雙擊
___block_literal_global
- 雙擊
0x0000000100006838
谦铃,查看invoke
- 雙擊
0x0000000100008008
,查看descriptor榔昔,和Block的源碼結(jié)構(gòu)類似
- 雙擊
如果block引用了外部變量呢驹闰?
定義一個(gè)block,其中block引用了外部變量撒会,查看此時(shí)的匯編代碼
int main(int argc, char * argv[]) {
int a = 10;
void(^block)(void) = ^(){
NSLog(@"block -- %d", a);
};
block();
return 0;
}
1嘹朗、lldb調(diào)試
-
以下是代碼的匯編
-
驗(yàn)證是否是block的isa指針
-
adrp x10, 2
獲取指針地址 -
ldr x10, [x10]
:取值
-
-
查看此時(shí)block的內(nèi)存,找到invoke(由于invoke是代碼實(shí)現(xiàn)诵肛,所以需要由
dis -s
(將代碼的匯編打印出來)查看)
2屹培、靜態(tài)分析
-
通過hopper靜態(tài)分析如下,以下是main函數(shù)的反匯編
-
雙擊
___main_block_invoke
曾掂,跳轉(zhuǎn)至invoke的具體實(shí)現(xiàn)(并沒有在main函數(shù)中惫谤,是單獨(dú)的實(shí)現(xiàn))
-
雙擊
___block_descriptor_36_e5_v8??0l
,是一個(gè)單獨(dú)的描述
總結(jié)
-
[[self alloc] init] 優(yōu)化過程
在最初的版本(iOS9)中珠洗,相當(dāng)于兩次消息發(fā)送
objc_msgSend
iOS11版本 是一次消息發(fā)送
objc_alloc + objc_msgSend
iOS13.5.1以上版本溜歪,已經(jīng)沒有objc_msgSend,而是
objc_alloc_init
-
反匯編分析方式:
通過
LLDB
動(dòng)態(tài)調(diào)試通過
Hopper + MachOView
靜態(tài)分析