
Qinz
創(chuàng)建一個對象是我們再熟悉不過的了,那么它轉(zhuǎn)換為匯編代碼又是怎么執(zhí)行的呢?接下來我們就通過最常見的創(chuàng)建對象入手,詳細(xì)分析對象創(chuàng)建和銷毀的匯編,從匯編中還原函數(shù)調(diào)用邏輯。
1. 首先我們來看下面一行代碼,這里就只是創(chuàng)建一個P對象:
- (void)viewDidLoad {
[super viewDidLoad];
Person* p = [[Person alloc]init];
}
- 1.1 上面是我們最熟悉的對象創(chuàng)建,我們知道,alloc和init都是發(fā)送消息,接下來我們斷點(diǎn)程序,查看到匯編代碼如下:
CS`-[ViewController viewDidLoad]:
0x10064a764 <+0>: sub sp, sp, #0x40 ; =0x40
0x10064a768 <+4>: stp x29, x30, [sp, #0x30]
0x10064a76c <+8>: add x29, sp, #0x30 ; =0x30
0x10064a770 <+12>: add x8, sp, #0x10 ; =0x10
0x10064a774 <+16>: adrp x9, 2
0x10064a778 <+20>: add x9, x9, #0xd00 ; =0xd00
0x10064a77c <+24>: adrp x10, 2
0x10064a780 <+28>: add x10, x10, #0xd30 ; =0xd30
0x10064a784 <+32>: stur x0, [x29, #-0x8]
0x10064a788 <+36>: stur x1, [x29, #-0x10]
-> 0x10064a78c <+40>: ldur x0, [x29, #-0x8]
0x10064a790 <+44>: str x0, [sp, #0x10]
0x10064a794 <+48>: ldr x10, [x10]
0x10064a798 <+52>: str x10, [sp, #0x18]
0x10064a79c <+56>: ldr x1, [x9]
0x10064a7a0 <+60>: mov x0, x8
0x10064a7a4 <+64>: bl 0x10064ab9c ; symbol stub for: objc_msgSendSuper2
0x10064a7a8 <+68>: adrp x8, 2
0x10064a7ac <+72>: add x8, x8, #0xd08 ; =0xd08
0x10064a7b0 <+76>: adrp x9, 2
0x10064a7b4 <+80>: add x9, x9, #0xd20 ; =0xd20
0x10064a7b8 <+84>: ldr x9, [x9]
0x10064a7bc <+88>: ldr x1, [x8]
0x10064a7c0 <+92>: mov x0, x9
0x10064a7c4 <+96>: bl 0x10064ab90 ; symbol stub for: objc_msgSend
0x10064a7c8 <+100>: adrp x8, 2
0x10064a7cc <+104>: add x8, x8, #0xd10 ; =0xd10
0x10064a7d0 <+108>: ldr x1, [x8]
0x10064a7d4 <+112>: bl 0x10064ab90 ; symbol stub for: objc_msgSend
0x10064a7d8 <+116>: mov x8, #0x0
0x10064a7dc <+120>: add x9, sp, #0x8 ; =0x8
0x10064a7e0 <+124>: str x0, [sp, #0x8]
0x10064a7e4 <+128>: mov x0, x9
0x10064a7e8 <+132>: mov x1, x8
0x10064a7ec <+136>: bl 0x10064abc0 ; symbol stub for: objc_storeStrong
0x10064a7f0 <+140>: ldp x29, x30, [sp, #0x30]
0x10064a7f4 <+144>: add sp, sp, #0x40 ; =0x40
0x10064a7f8 <+148>: ret
- 1.2 可以看到上面簡單的一句代碼轉(zhuǎn)換為了很多條匯編指令,匯編指令是與機(jī)器碼一一對應(yīng)的,每執(zhí)行一條匯編指令就是一個通電放電的過程,所以每條匯編執(zhí)行執(zhí)行的時間幾乎相等。上面是在arm64架構(gòu)下的匯編代碼,不同的指令集對應(yīng)的匯編代碼會有所不同。接下來會分段剖析該匯編指令。
2. 首先分析前4條指令
//拉伸64字節(jié)??丶瑂p為指向棧底的寄存器
0x104802764 <+0>: sub sp, sp, #0x40 ; =0x40
// x29和x30寄存器入棧
0x104802768 <+4>: stp x29, x30, [sp, #0x30]
//sp指向48字節(jié)處位置,存入x29寄存器
0x10480276c <+8>: add x29, sp, #0x30 ; =0x30
//sp從#0x30處偏移#0x10,即指向16直接處
0x104802770 <+12>: add x8, sp, #0x10 ; =0x10
-
2.1 關(guān)于寄存器,這里簡單說下,如下圖:寄存器
-
2.2 如上圖,iOS中主要有異常處理寄存器、浮點(diǎn)寄存器以及通用寄存器。通用寄存器也稱數(shù)據(jù)地址寄存器,通常用來做數(shù)據(jù)計算的臨時存儲、累加、計數(shù)、地址保存等功能,定義這些寄存器的作用主要是用于在CPU指令中保存操作數(shù),在CPU中當(dāng)做一些常規(guī)變量來使用。ARM64擁有有32個64位的通用寄存器 x0 到 x30,以及XZR(零寄存器),這些通用寄存器有時也有特定用途,w0 到 w28 這些是32位的. 因?yàn)?4位CPU可以兼容32位.所以可以只使用64位寄存器的低32位。而在XCode中并沒有看到X29和X30寄存器,如下圖:通用寄存器
-
2.3 其實(shí)這里的fp就是x29,lr就是x30寄存器,只不過被蘋果重新進(jìn)行了命名。x29在某些時刻會保存棧頂?shù)牡刂?,sp會一直保存棧底的地址,lr(x30)保存下一條指令執(zhí)行的地址,pc指向當(dāng)前指令的地址,cpsr為狀態(tài)寄存器。如下圖:寄存器
-
2.4 為了更形象理解上面4條指令,繪制棧空間分配圖如下:拉伸??臻g
3. 接下來繼續(xù)往下看,x9寄存器放置ViewDidLoad方法的地址
//將地址 0x104802774 左移三位 即 0x104802000 ,讓后加2,即0x104804000
0x104802774 <+16>: adrp x9, 2
//將后三位偏移加上0xd00 ,得到地址0x104804d00
0x104802778 <+20>: add x9, x9, #0xd00 ; =0xd00
-
3.1 通過算出該地址,我們即可以得到該方法名,如下圖:x9寄存器
-
3.2 當(dāng)然這里讀取x1的值也是該方法,因?yàn)樽詈髕9會被讀到x1中。默認(rèn)函數(shù)的調(diào)用者被放在x0寄存器,方法地址被放在x1寄存器。x1寄存器
- 3.3 通過相同的方法,我們分析接下來的兩句指令:
0x10064a77c <+24>: adrp x10, 2
0x10064a780 <+28>: add x10, x10, #0xd30 ; =0xd30
-
3.3 寄存器 x10放置self對象的地址:x10寄存器
4. 將x0和x1寄存器入棧:
0x10064a784 <+32>: stur x0, [x29, #-0x8]
0x10064a788 <+36>: stur x1, [x29, #-0x10]
-
4.1 x0和x1入棧圖:x0和x1入棧
5. 將x29的值讀到x0中,x0入棧:
-> 0x10064a78c <+40>: ldur x0, [x29, #-0x8]
0x10064a790 <+44>: str x0, [sp, #0x10]
6. 從棧區(qū)取出X10,也就是上面的self,然后再將x10拉伸24字節(jié),我們可以看到,后面沒有對X10這個寄存器的操作呢,說明對x10進(jìn)行入棧保護(hù),即當(dāng)前控制器的地址在頁面沒銷毀的情況下是一直強(qiáng)持有的。
0x10064a794 <+48>: ldr x10, [x10]
0x10064a798 <+52>: str x10, [sp, #0x18]
7. 將X9讀到x1寄存器中,也就是上面的ViewDidLoad。然后將X8移動到X0寄存器,這樣就將X8和X9兩個寄存器給空出來,后面的函數(shù)進(jìn)來就可以利用這一塊空間了。
0x10064a79c <+56>: ldr x1, [x9]
0x10064a7a0 <+60>: mov x0, x8
8. 接下來可以看到objc_msgSendSuper2,就是調(diào)用 [super viewDidLoad]這個方法了,bl指令會調(diào)到方法內(nèi)部,并且該命令會保存上一條指令。
0x10064a7a4 <+64>: bl 0x10064ab9c ; symbol stub for: objc_msgSendSuper2
9. 接著x8寄存器就存儲alloc方法的地址了,如下圖:
x8被再次利用

x8被再次利用
10. 然后x9就用來存儲對象的地址了,如下圖:
x9被再次利用

x9被再次利用
11. 接著調(diào)用objc_msgSend發(fā)送alloc消息:

12. 當(dāng)alloc調(diào)用完畢后,x8寄存器又被重復(fù)利用到存儲init方法的地址:
x8再次被利用

x8再次被利用
13. 以上重點(diǎn)分析的是通過匯編看函數(shù)調(diào)用,接下來下面這6句匯編指令,就是和內(nèi)存管理相關(guān)的:
//將x8賦值為0,即x8 = nil
0x10064a7d8 <+116>: mov x8, #0x0
//x9 = x9 + sp(#0x8)
0x10064a7dc <+120>: add x9, sp, #0x8 ; =0x8
//x0入棧
0x10064a7e0 <+124>: str x0, [sp, #0x8]
//x0 = x9,x1 = x8作為參數(shù)傳入objc_storeStrong函數(shù)
-> 0x10064a7e4 <+128>: mov x0, x9
0x10064a7e8 <+132>: mov x1, x8
0x10064a7ec <+136>: bl 0x10064abc0 ; symbol stub for: objc_storeStrong
14. 通過查看x9寄存器的地址,可以得出x9寄存器保存的是person對象的指針:
保存指針的地址

保存指針的地址
15. 所以這里的兩個參數(shù)x0和x1分別為&p和nil,偽代碼如下:
func(&p,nil);
16. 為了了解這個objc_storeStrong函數(shù)在做什么,我們就要去objc源碼去查看,該函數(shù)的源碼如下:
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
17. 所以將p對象傳入方法后就等價于下面的代碼了:
void
objc_storeStrong(&p, nil)
{
id p = *&p;
if (obj == nil) {
return;
}
objc_retain(nil);
&p = nil;
objc_release(p);
}
@end
-
17.1 內(nèi)存管理如下圖:內(nèi)存管理
18. 最后三句匯編指令代表該函數(shù)的結(jié)束,即平衡??臻g:
0x10064a7f0 <+140>: ldp x29, x30, [sp, #0x30]
0x10064a7f4 <+144>: add sp, sp, #0x40 ; =0x40
0x10064a7f8 <+148>: ret
-
18.1 函數(shù)調(diào)用的棧空間變化圖:??臻g變化









