反匯編OC代碼看函數(shù)調(diào)用及內(nèi)存管理

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被再次利用
10. 然后x9就用來存儲對象的地址了,如下圖:
x9被再次利用
11. 接著調(diào)用objc_msgSend發(fā)送alloc消息:
12. 當(dāng)alloc調(diào)用完畢后,x8寄存器又被重復(fù)利用到存儲init方法的地址:
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變化
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容