iOS 匯編入門學(xué)習(xí)

最近看一些資料,接觸到一些資料有匯編陷揪,發(fā)現(xiàn)看不懂惋鸥,還是有必要學(xué)習(xí)下。
在iOS中悍缠,消息發(fā)送是匯編寫的揩慕,在學(xué)習(xí)戴銘高手課時hook oc中的objc_send方法,也用到匯編扮休。嘗試去閱讀,網(wǎng)上也有很多注釋拴鸵,發(fā)現(xiàn)看完之后依然一知半解玷坠。還是從基礎(chǔ)學(xué)起蜗搔。

1. 先看一段C的代碼,我們可以放到main.m中:

int addFunction(int a, int b) {
    int c = a + b;
    return c;
}

2. 選擇真機八堡,Xcode選中Assemble轉(zhuǎn)成匯編代碼

Xcode選中Assemble

這樣我們可以看到匯編樟凄,注意一定要選擇Generic iOS Device,模擬器或真機轉(zhuǎn)成的匯編和本文有差異兄渺。

3. _addFunction:

忽略掉.file缝龄、.loc、.cfi_startproc挂谍,可得到以下相關(guān)匯編代碼:

     sub    sp, sp, #16             ; =16
     
     str    w0, [sp, #12] 
     str    w1, [sp, #8] 
     
     ldr    w0, [sp, #12] 
     ldr    w1, [sp, #8] 
     
     add    w0, w0, w1 
 
     str    w0, [sp, #4]
     ldr    w0, [sp, #4]
     
     add    sp, sp, #16             ; =16 
     ret

為什么是w0,w1呢叔壤,不是r0,r1或x0,x1呢?
w0 - w30訪問時口叙,訪問的是這些寄存器的低32位炼绘。當(dāng)使用 r0 - r30 訪問時,它就是一個64位的數(shù)妄田。

3.1 添加注釋后的匯編

首先俺亮,分配棧所需的所有臨時存儲空間。棧是一大塊函數(shù)隨時想使用的內(nèi)存疟呐。
ARM中的棧內(nèi)存是高地址向低地址分布的脚曾,意味著你必須從棧指針開始減。
在這里启具,分配了16個字節(jié)長度的內(nèi)存本讥。
    sub sp, sp, #16             ; =16

這里,兩個參數(shù)被存入棧中富纸。這是通過存儲寄存指令(str)實現(xiàn)的囤踩。
第一個參數(shù)是要存儲的寄存器
第二個是存儲的位置。方括號代表里面值是內(nèi)存地址晓褪。 這個方括號指令允許你為一個值指定偏移量堵漱,
因此[sp, #12]的意思『在棧指針的地址上加上12字節(jié)偏移量』
同樣地,str w0, [sp, #12]意味著『存儲w0寄存器的值到棧指針地址加上12字節(jié)內(nèi)存的位置』涣仿。
    str w0, [sp, #12]
    str w1, [sp, #8]
 
剛被保存到棧的值又被讀取到相同的寄存器內(nèi)勤庐。和str指令相反的,ldr指令是從一個內(nèi)存中加載內(nèi)容到寄存器好港。
ldr w0, [sp, #12]意思是『讀取出在棧指針地址加上12字節(jié)內(nèi)存的位置的內(nèi)容愉镰,并將內(nèi)容賦值給寄存器w0』。 
如果你好奇為何w0和w1剛被存儲又被加載出來钧汹,對丈探,它是很奇怪,這兩行明明就是多余的嘛拔莱!如果編譯器允許基本的編譯優(yōu)化碗降,那么這多余的就會被消除隘竭。
    ldr w0, [sp, #12]
    ldr w1, [sp, #8]
 
意思是將w0和w1中的內(nèi)容相加,并將相加的值賦值給r0讼渊。 
add指令入?yún)⒖梢允莾蓚€或者三個动看,如果是三個,那第一個參數(shù)就是存儲后兩個參數(shù)相加的值的寄存器爪幻。
所以菱皆,這行指令也可以寫成:add w0, w0, w1。
    add w0, w0, w1
 
再一次挨稿,編譯器生成了一些多余的代碼:將相加的結(jié)果存儲起來仇轻,又讀取到相同的位置。
    str w0, [sp, #4]
    ldr w0, [sp, #4]

函數(shù)即將終止叶组,因此棧指針放回原來的地方拯田。
函數(shù)開始時從sp(棧指針)上減去了12個字節(jié)而得到12個字節(jié)內(nèi)存使用。現(xiàn)在它把12個字節(jié)還回去甩十。
函數(shù)必須保證棧指針操作平衡船庇,否則棧指針可能漂移,最終可能超出了已分配的內(nèi)存侣监。你應(yīng)該不希望那樣...
    add sp, sp, #16             ; =16
    ret

3.2 簡單點理解

     sub    sp, sp, #16             ; =16 //棧地址減去16鸭轮,即分配了16字節(jié)內(nèi)存
     
     str    w0, [sp, #12] //把w0存儲到sp棧中,sp指針上加上12字節(jié)的偏移量
     str    w1, [sp, #8] //把w1存儲到sp棧中橄霉,sp指針上加上8字節(jié)的偏移量
     
     ldr    w0, [sp, #12] //讀取棧中12字節(jié)偏移量的地址到存儲器w0中
     ldr    w1, [sp, #8] //讀取棧中8字節(jié)偏移量的地址到存儲器w1中
     
     add    w0, w0, w1 //w0的值加上w1的值存儲到w0中
 
     str    w0, [sp, #4]//把w0的值存儲到sp棧中
     ldr    w0, [sp, #4]//讀取sp棧中的值到w0中
     
     add    sp, sp, #16             ; =16 //棧地址加16字節(jié)窃爷,即回收分配的內(nèi)存
     ret //結(jié)束

以上是未優(yōu)化的匯編代碼,有很多重復(fù)且沒有用的代碼。

3.3 Xcode選擇release姓蜂,編譯器優(yōu)化后:

    add w0, w1, w0
    ret

小結(jié):

  1. iOS中對象內(nèi)存是分配在堆上的按厘,局部變量或指針都是在棧上的。OC代碼都會被機器編譯成匯編钱慢,不考慮復(fù)雜的場景逮京,匯編都是和棧的內(nèi)存進(jìn)行打交道。匯編也和CPU的寄存器打交道束莫。
  2. 局部變量的值或地址在棧中懒棉,而真正的計算是在寄存器中的,使用時需要分配空間览绿,即sub sp, sp, #16 ;寄存器可以把值存儲到棧中str w0, [sp, #12];寄存器也可以從棧中取出值策严,即 ldr w1, [sp, #8];寄存器和寄存器之間也可以相互操作;用完需要釋放內(nèi)存,不然會有內(nèi)存泄露饿敲,即add sp, sp, #1妻导。

參考文章:iOS匯編教程

4. arm64寄存器簡單介紹

64位處理器有34個寄存器,包括31個通用寄存器、SP倔韭、PC暑脆、CPSR。


register

x0-x7: 用于子程序調(diào)用時的參數(shù)傳遞狐肢,x0 還用于返回值傳遞
x0 - x30 是31個通用整形寄存器。每個寄存器可以存取一個64位大小的數(shù)沥曹。 當(dāng)使用 r0 - r30 訪問時份名,它就是一個64位的數(shù)。當(dāng)使用 w0 - w30 訪問時妓美,訪問的是這些寄存器的低32位

查看x0返回值,0x2e=46


register read x0

pc:表示當(dāng)前執(zhí)行的指令的地址

(lldb) register read pc
      pc = 0x00000001022c6c50  DataStructureDemo`addFunction + 28 at main.m:14:12

lr:鏈接寄存器僵腺,存放著函數(shù)的返回地址:這里存放的是fooFunction地址

(lldb) register read lr
      lr = 0x00000001022c6c74  DataStructureDemo`fooFunction + 24 at main.m:18:9

5. _fooFunction:

在上面的代碼中添加下面函數(shù),并調(diào)用addFunction

void fooFunction() {
    int add = addFunction(12, 34);
    printf("add = %i", add);
}

5.1 添加注釋后的匯編

    push    {r7, lr} //r7,lr入棧
    mov r7, sp//r7=sp即r7保存了棧頂元素
    sub sp, #8//sp減8字節(jié)
 
    movs    r0, #12 //r0 = 12
    movs    r1, #34//r1 = 34
    bl  _addFunction //調(diào)用函數(shù)addFunction; r0,r1是addFunction兩個參數(shù)
    
    str r0, [sp, #4]//r0是返回結(jié)果,把r0存儲到sp中
    ldr r1, [sp, #4]//取出sp給r1;這兩句等價于r1=r0;
    
    movw    r0, :lower16:(L_.str-(LPC2_0+4))
    movt    r0, :upper16:(L_.str-(LPC2_0+4))
 //printf函數(shù)的第一個參數(shù)是一個字符串,可以搜索L_.str壶栋,看到.asciz "add = %i"
 //前兩個指令加載常量的地址辰如,并減去標(biāo)簽的地址(LPC1_0加上4字節(jié))。
    add r0, pc
     //r0加上pc(程序計數(shù)器)贵试,這樣無論L.str在二進(jìn)制文件的什么位置都能夠準(zhǔn)確的存放字符串的位置琉兜。
     //上面三條指令加載指向所需的字符串的開始地址的指針到r0寄存器。

    bl  _printf//執(zhí)行printf函數(shù),r0是參數(shù)毙玻,且字符串已拼接好
 
    str r0, [sp]                @ 4-byte Spill//存儲r0到棧中
    add sp, #8//恢復(fù)棧內(nèi)存
    pop {r7, pc}//恢復(fù)r7,pc

6.OC函數(shù)

前面的寫法都是C語言的寫法豌蟋,OC與C還是有一定區(qū)別∩L玻看下面源碼:

- (int)addValue:(int)a toValue:(int)b {
    int c = a + b;
    return c;
}

6.1優(yōu)化版本匯編

adds    r0, r3, r2
bx  lr

r3,r2是參數(shù)a和b,為什么不是r0,r1呢?
因為在OC中有兩個隱士的參數(shù):id self, SEL _cmd梧疲。

6.2 foo函數(shù)

- (void)foo {
    int add = [self addValue:12 toValue:34];
    NSLog(@"add = %i", add);
}

轉(zhuǎn)換后的匯編:

 push    {r7, lr}//r7,lr入棧
 mov    r7, sp//r7=sp

 Ltmp10:
 movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))
 
 Ltmp11:
 movs    r2, #12//r2=12
 movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))//查找_cmd
 movs    r3, #34//r3=34
 
 LPC4_0:
 add    r1, pc//r1=r1+pc
 ldr    r1, [r1]//表示加載存儲在r1指針內(nèi)的內(nèi)容并賦值給r1。用偽代碼表示r1=*r1

 bl    _objc_msgSend//調(diào)用objc_msgsend
 Ltmp12:
 mov    r1, r0//r1=ro
 
 Ltmp13:
 movw    r0, :lower16:(L__unnamed_cfstring_-(LPC4_1+4))
 movt    r0, :upper16:(L__unnamed_cfstring_-(LPC4_1+4))
 //給r0賦值,r0=self;
 LPC4_1:
 add    r0, pc//r0=r0+pc
 bl    _NSLog//調(diào)用NSLog
 
 Ltmp14:
 pop    {r7, pc}//出棧
  1. movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))
    movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC4_0+4))//查找_cmd
    add r1, pc//r1=r1+pc
    這三句放一塊解讀运准,沒什么問題幌氮,r1存入當(dāng)前selector的字符。selector的引用:其實selector就是存儲在數(shù)據(jù)段的字符串胁澳。

  2. movw r0, :lower16:(L__unnamed_cfstring_-(LPC4_1+4))
    movt r0, :upper16:(L__unnamed_cfstring_-(LPC4_1+4))
    add r0, pc//r0=r0+pc
    這三句與r1的三句類似该互,r0=self。

3.總的上面步驟:r0=self,r1=_cmd,r2=12,r3=34;調(diào)用objc_msgSend,調(diào)用NSLog听哭。整體流程清晰明了慢洋。

總結(jié):
1.至此大概了解了OC的整個匯編的過程.舉一反三,看下viewDidLoad方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

轉(zhuǎn)成匯編:

    push {r7, lr}
    mov r7, sp
//只要是方法里調(diào)用了別的方法,上面兩句少不了陆盘。

    sub sp, #8//分配1個字節(jié)

    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
Ltmp1:
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
//r1=cmd

    movw    r2, :lower16:(L_OBJC_CLASSLIST_SUP_REFS_$_-(LPC0_1+4))
    movt    r2, :upper16:(L_OBJC_CLASSLIST_SUP_REFS_$_-(LPC0_1+4))
//r2=super
    add r1, pc//r1+=pc
    add r2, pc//r2+=pc

    ldr r1, [r1]//r1=*r1;表示加載存儲在r1指針內(nèi)的內(nèi)容并賦值給r1
    ldr r2, [r2]//r2=*r2;表示加載存儲在r2指針內(nèi)的內(nèi)容并賦值給r2
    strd    r0, r2, [sp]//str r0, [sp];str r2, [sp + 4]即r0普筹,r2存儲到sp中
    mov r0, sp//r0=sp

    bl  _objc_msgSendSuper2//調(diào)用super方法

    add sp, #8//恢復(fù)棧指針
    pop {r7, pc}//恢復(fù)r7,pc

如果是匯編,能不能反推到正常的代碼邏輯呢隘马,找到bl即找到了調(diào)用的方法太防,再找到r0,r1,r2等參數(shù),知道方法的參數(shù)。也許復(fù)雜的邏輯很難蜒车,但孰能生巧讳嘱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酿愧,隨后出現(xiàn)的幾起案子沥潭,更是在濱河造成了極大的恐慌,老刑警劉巖嬉挡,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钝鸽,死亡現(xiàn)場離奇詭異,居然都是意外死亡庞钢,警方通過查閱死者的電腦和手機拔恰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來基括,“玉大人颜懊,你說我怎么就攤上這事》缑螅” “怎么了河爹?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長揪阶。 經(jīng)常有香客問我昌抠,道長,這世上最難降的妖魔是什么鲁僚? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任炊苫,我火速辦了婚禮,結(jié)果婚禮上冰沙,老公的妹妹穿的比我還像新娘侨艾。我一直安慰自己,他們只是感情好拓挥,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布唠梨。 她就那樣靜靜地躺著,像睡著了一般侥啤。 火紅的嫁衣襯著肌膚如雪当叭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天盖灸,我揣著相機與錄音蚁鳖,去河邊找鬼。 笑死赁炎,一個胖子當(dāng)著我的面吹牛醉箕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼讥裤,長吁一口氣:“原來是場噩夢啊……” “哼放棒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起己英,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤间螟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后损肛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寒亥,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年荧关,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褂傀。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡忍啤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仙辟,到底是詐尸還是另有隱情同波,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布叠国,位于F島的核電站未檩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粟焊。R本人自食惡果不足惜冤狡,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望项棠。 院中可真熱鬧悲雳,春花似錦、人聲如沸香追。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽透典。三九已至晴楔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間峭咒,已是汗流浹背税弃。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讹语,地道東北人钙皮。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親短条。 傳聞我的和親對象是個殘疾皇子导匣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容