前言
我們使用Hopper Disassembler等反編譯工具查看Mach-O可執(zhí)行文件時(shí),看到的都是匯編代碼,所以只有我們學(xué)會(huì)了匯編后,才能更好的去調(diào)試,去分析App的邏輯
學(xué)會(huì)了匯編之后,甚至可以直接修改匯編代碼,導(dǎo)出可執(zhí)行文件,再經(jīng)過(guò)簽名安裝使用了,都不用通過(guò)之前的tweak方式hook了,當(dāng)然,這需要對(duì)匯編掌握的及其深入,并且經(jīng)過(guò)調(diào)試分析清楚了App整體邏輯之后了。
一、匯編
- CPU的內(nèi)部有寄存器、運(yùn)算器和控制器,它們之間由總線連接。其中運(yùn)算器負(fù)責(zé)信息處理,由CPU硬編碼指令完成;控制器負(fù)責(zé)協(xié)調(diào)控制計(jì)算機(jī)的其他器件進(jìn)行工作;寄存器進(jìn)行數(shù)據(jù)的臨時(shí)存儲(chǔ),程序員只需關(guān)心寄存器的數(shù)據(jù)存取即可改變運(yùn)行CPU運(yùn)行結(jié)果。
- 在iOS領(lǐng)域,根據(jù)CPU架構(gòu)不同,匯編主要分為兩種:
模擬器上的x86匯編、真機(jī)上的arm64匯編,這里我們重點(diǎn)介紹真機(jī)上的arm64匯編
- 在iOS領(lǐng)域,根據(jù)CPU架構(gòu)不同,匯編主要分為兩種:
- 想要學(xué)好
arm64匯編,主要學(xué)三個(gè)方面:寄存器、指令、堆棧
- 想要學(xué)好
二、寄存器
寄存器就是CPU內(nèi)部臨時(shí)存儲(chǔ)數(shù)據(jù)的地方,分為有很多種,如下圖所示,我們下面依次講解:

-
我們把
x0~x28稱為通用寄存器,通用寄存器通常用來(lái)存放一般性的數(shù)據(jù),通用寄存器分為64位和32位64位(8字節(jié))的通用寄存器有:x0 ~ x28,29個(gè)寄存器,8個(gè)字節(jié)代表這個(gè)寄存器最多能放8個(gè)字節(jié)的數(shù)據(jù)32位(4字節(jié))的通用寄存器有:w0 ~ w28,這29個(gè)4字節(jié)的寄存器其實(shí)是8字節(jié)寄存器x0 ~ x28的低32位-
通用寄存器的x0與w0之間的關(guān)系,如下圖所示,其他通用寄存器也是如此
x0寄存器與w0寄存器的關(guān)系 -
通過(guò)LLDB命令,我們也可以驗(yàn)證出他們的關(guān)系,如下圖所示:
讀取x0與w0寄存器.png
寫(xiě)入再讀取x0與w0寄存器.png x0~x7寄存器,一般會(huì)存儲(chǔ)函數(shù)的參數(shù),大于8個(gè)的會(huì)通過(guò)堆棧傳參
x0:一般會(huì)存儲(chǔ)函數(shù)的返回值
-
- pc寄存器,也叫做程序計(jì)數(shù)器,記錄著當(dāng)前CPU執(zhí)行的是哪一條指令,存儲(chǔ)著當(dāng)前CPU正在執(zhí)行指令的地址,類似于8086匯編的ip寄存器
-
堆棧寄存器,有兩個(gè),分別是:
棧頂指針寄存器sp和棧低指針寄存器fp,棧低指針寄存器fp也叫做x29寄存器,堆棧寄存器是用來(lái)控制函數(shù)分配棧空間的
-
堆棧寄存器,有兩個(gè),分別是:
-
鏈接寄存器lr,也叫做
x30寄存器,存儲(chǔ)著函數(shù)的返回地址
-
鏈接寄存器lr,也叫做
-
-
程序狀態(tài)寄存器,有兩種,分別是
cpsr(current Program Status Register)和spsr(Saved Program Status Register),cpsr是當(dāng)前程序的運(yùn)行狀態(tài),spsr是在異常狀態(tài)下使用的,CPSR寄存器和其他寄存器不一樣(其他寄存器是用來(lái)存放數(shù)據(jù)的,都是整個(gè)寄存器具有一個(gè)含義),是按位起作用的,也就是說(shuō),它的每一位都有專門(mén)的含義,記錄特定的信息,如下圖所示:
spsr的各位- CPSR的低8位(包括I、F、T和M[4~0])稱為控制位,程序無(wú)法修改,除非CPU運(yùn)行于特權(quán)模式下,程序才能修改控制位! - N、Z、C、V均為條件碼標(biāo)志位。它們的內(nèi)容可被算術(shù)或邏輯運(yùn)算的結(jié)果所改變,并且可以決定某條指令是否被執(zhí)行,意義重大! -
程序狀態(tài)寄存器,有兩種,分別是
-
xzr零寄存器,表示
zero register,里面存儲(chǔ)的值都是0,xzr代表8字節(jié),wzr代表4字節(jié)
-
xzr零寄存器,表示
-
總結(jié):
x0 ~ x28是通用寄存器,有8個(gè)字節(jié)的空間,其中較低的4個(gè)字節(jié)的空間,是w0~w28寄存器,x代表8個(gè)字節(jié),w代表4個(gè)字節(jié)x29就是fp寄存器,也就是棧底寄存器,代表著函數(shù)的棧底x30就是lr寄存器,也就是鏈接寄存器,存儲(chǔ)著函數(shù)的返回地址,使用bl指令跳轉(zhuǎn)時(shí),會(huì)把bl的下面的一條指令的地址,存放到lr寄存器中,如果bl跳轉(zhuǎn)之后,遇到了ret指令,ret指令會(huì)把lr寄存器的值給pc寄存器,這樣CPU就會(huì)執(zhí)行lr寄存器存放的指令了,就相當(dāng)于函數(shù)返回了
-
總結(jié):
三、指令
-
mov指令,傳送指令,格式是
mov {條件}{S}目標(biāo)寄存器 ,源操作數(shù),mov指令可以完成從另一個(gè)寄存器或者是將一個(gè)立即數(shù),加載到目的寄存器中,其中S選項(xiàng)決定執(zhí)行的操作是否影響CPSR中條件標(biāo)志位的值,當(dāng)沒(méi)有S時(shí)指令不更新CPSR中的條件標(biāo)志位的值。mov指令實(shí)例如下:
mov x1,x0,意思是將寄存器x0的值傳送到寄存器x1中mov pc,x14,意思是將寄存器x14的值傳送到寄存器pc中,pc寄存器通常用來(lái)存放CPU正在執(zhí)行的指令的地址,所以常用于子程序返回mov x1,x0,#0x3,意思是將寄存器x0的值加上0x3,然后傳送到寄存器x1中
-
-
ret指令,返回指令,作用是函數(shù)返回,本質(zhì)上是將
lr(x30)寄存器的值賦值給pc寄存器,pc寄存器存儲(chǔ)CPU當(dāng)前執(zhí)行的指令的地址,lr寄存器存儲(chǔ)著函數(shù)的返回地址,將lr寄存器的值,給了pc寄存器,相當(dāng)于實(shí)現(xiàn)了函數(shù)返回
-
ret指令,返回指令,作用是函數(shù)返回,本質(zhì)上是將
-
add指令,加法指令,格式是
add {條件}{S}目標(biāo)寄存器 ,操作數(shù)1,操作數(shù)2,add指令用于將 兩個(gè)操作數(shù)相加,并將結(jié)果存放到目的寄存器中,操作數(shù)1必須是一個(gè)寄存器,操作數(shù)2可以是寄存器,也可以是立即數(shù)add指令實(shí)例如下:
add x0,x1,x3,意思是將寄存器x1的值加上寄存器x3的值,賦值給寄存器x0add x0,x1,#0x77,意思是將寄存器x1的值加上0x77,然后賦值給寄存器x0add x0,x1,x3,LSL0x1,意思是將寄存器x1的值加上寄存器x3左移0x1,然后賦值給寄存器x0,也就是:x0 = x1 + (x3 << 1)
-
-
sub指令,減法指令,格式是
sub {條件}{S}目標(biāo)寄存器 ,操作數(shù)1,操作數(shù)2,sub指令用于把操作數(shù)1減去操作數(shù)2,并將結(jié)果存放到目的寄存器中,操作數(shù)1必須是一個(gè)寄存器,操作數(shù)2可以是寄存器,也可以是立即數(shù)sub指令實(shí)例如下:
sub x0,x1,x3,意思是將寄存器x1的值減去寄存器x3的值,賦值給寄存器x0sub x0,x1,#0x88,意思是將寄存器x1的值減去0x88,然后賦值給寄存器x0sub x0,x1,x3,LSL0x1,意思是將寄存器x1的值減去寄存器x3左移0x1,然后賦值給寄存器x0,也就是:x0 = x1 - (x3 << 1)
-
-
cmp指令,比較指令,格式是
cmp {條件}{S}目標(biāo)寄存器 ,操作數(shù)1,操作數(shù)2,cmp指令用于把一個(gè)寄存器的內(nèi)容和另一個(gè)寄存器的內(nèi)容或者立即數(shù),進(jìn)行比較,同時(shí)更新CPSR中的條件標(biāo)志位,cmp指令進(jìn)行的是一次減法運(yùn)算,不會(huì)存儲(chǔ)結(jié)果,只會(huì)更改條件標(biāo)志位。cmp指令實(shí)例如下:
cmp x0,x1,意思是將寄存器x0的值與寄存器x1的值相減,并根據(jù)結(jié)果設(shè)置CPSR的標(biāo)志位cmp x0,#0x88,意思是將寄存器x0的值與立即數(shù)0x88相減,并 根據(jù)結(jié)果設(shè)置CPSR的標(biāo)志位
-
-
b指令,不帶返回的跳轉(zhuǎn)指令,常與
cmp指令配合使用,格式是b {條件} 目的地址,一旦遇到b指令,ARM處理器將立即跳轉(zhuǎn)到目的地址,從那里繼續(xù)執(zhí)行,注意存儲(chǔ)在跳轉(zhuǎn)指令中的是相對(duì)于當(dāng)前pc值的一個(gè)偏移量,而不是一個(gè)絕對(duì)地址,偏移量是由匯編器來(lái)計(jì)算的。
-
b指令,不帶返回的跳轉(zhuǎn)指令,常與
-
bl 指令,帶返回的跳轉(zhuǎn)指令,常與
cmp指令配合使用,格式是bl {條件} 目的地址,bl 指令的格式為:bl{條件} 目標(biāo)地址,bl是另一個(gè)跳轉(zhuǎn)指令,在跳轉(zhuǎn)之前,會(huì)把下一條指令的地址,存儲(chǔ)到lr寄存器中,等到子函數(shù)執(zhí)行完畢,執(zhí)行ret指令時(shí),ret指令會(huì)把lr寄存器的值給了pc寄存器,pc寄存器存儲(chǔ)的是CPU當(dāng)前執(zhí)行的指令的地址,這樣就實(shí)現(xiàn)了子函數(shù)返回。例如:cmp與b配合使用,如下所示
-
0x100432624 <+88>: cmp x1, #0x1 ; =0x1
0x100432628 <+92>: b.le 0x100432630 ;
<1>. cmp: 將寄存器 x1 的值與立即數(shù) 0x1 相減,并根據(jù)結(jié)果設(shè)置 CPSR 的標(biāo)志位
<2>. b.le 0x100432630:表示如果x1 <= 0x1那么就執(zhí)行0x100432630
-
ldr 指令,從內(nèi)存加載數(shù)據(jù)到寄存器,格式是:
ldr{條件} 目的寄存器,<存儲(chǔ)器地址>,ldr指令用于從存儲(chǔ)器中將讀取相應(yīng)大小的字節(jié)數(shù),傳送到目的寄存器中ldr指令實(shí)例如下:
ldr x0,[x1],意思是從x1寄存器存儲(chǔ)的地址取出8個(gè)字節(jié),然后存到x0寄存器中ldr w0,[x1],意思是從x1寄存器存儲(chǔ)的地址取出4個(gè)字節(jié),然后存到w0寄存器中ldr x0,[x1,#0x888],意思是從x1寄存器存儲(chǔ)的值加上0x888得到地址處,取出8個(gè)字節(jié),然后存到x0寄存器中ldr x0,[x1,#0x888]!,意思是從x1寄存器存儲(chǔ)的值加上0x888得到地址處,取出8個(gè)字節(jié),然后存到x0寄存器中,并且將新地址x1+0x888,存入到x1寄存器中
-
-
str 指令,將寄存器的數(shù)據(jù)存儲(chǔ)到內(nèi)存中,格式是:
str{條件} 源寄存器,<存儲(chǔ)器地址>str指令實(shí)例如下:
str x0,[x1],意思是從x0寄存器存儲(chǔ)的地址取出8個(gè)字節(jié),然后存到x1寄存器值的內(nèi)存中ldr w0,[x1,#0x888],意思是從w0寄存器存儲(chǔ)的地址取出4個(gè)字節(jié),然后存到x1寄存器的值加上0x888的地址中
-
三、葉子函數(shù)的堆棧
- 函數(shù)分為:葉子函數(shù)和非葉子函數(shù),這兩種函數(shù)的堆棧分配情況略有不同,葉子函數(shù)就是像葉子一樣沒(méi)有分支了,函數(shù)內(nèi)部不會(huì)調(diào)用其他函數(shù)了;非葉子函數(shù),就是函數(shù)內(nèi)部會(huì)調(diào)用其他函數(shù)。
- 我們先來(lái)看一個(gè)葉子函數(shù),C代碼和匯編代碼如下:(w代表4個(gè)字節(jié),x代表8個(gè)字節(jié),;代表注釋)
C代碼:
void test(){
int a = 2;
int b = 3;
}
test的函數(shù)匯編代碼如下:
sub sp, sp, #16 ; =16 ;sp = sp - 16,開(kāi)辟16個(gè)字節(jié)的??臻g
orr w8, wzr, #0x2 ;設(shè)置w8寄存器為2
str w8, [sp, #12] ;在sp地址偏移12個(gè)字節(jié)的地方開(kāi)始存儲(chǔ)4個(gè)字節(jié)的數(shù)據(jù),也就是w8寄存器的值,也就是存儲(chǔ)了2
orr w8, wzr, #0x3 ;設(shè)置w8寄存器為3
str w8, [sp, #8] ;在sp指針+8的地址開(kāi)始,存儲(chǔ)4個(gè)字節(jié)的數(shù)據(jù),也就是將w8的值存儲(chǔ)到sp偏移8字節(jié)的位置
add sp, sp, #16 ; =16 ;sp = sp + 16,回收16個(gè)字節(jié)的空間,保持堆棧平衡
ret
-
葉子函數(shù)的堆棧開(kāi)辟,可以用下面一幅圖表示:
image.png
-
四、非葉子函數(shù)的堆棧
- 我們?cè)賮?lái)看一個(gè)非葉子函數(shù),C代碼和匯編代碼如下:(w代表4個(gè)字節(jié),x代表8個(gè)字節(jié),;代表注釋)
C代碼如下,我們?cè)趃ood函數(shù)里,調(diào)用了test函數(shù)
void test(){
int a = 2;
int b = 3;
}
void good(){
int a = 7;
int b = 8;
test();
}
good函數(shù)的匯編代碼如下:
sub sp, sp, #32 ; =32 ;sp = sp-32,開(kāi)辟函數(shù)??臻g,也就是將sp指針往低字節(jié)移動(dòng)32字節(jié)
stp x29, x30, [sp, #16] ; 16-byte Folded Spill ; 在sp+16的地方,往高字節(jié)的方向,依次存放x29、x30的數(shù)據(jù),x29的數(shù)據(jù)占8個(gè)字節(jié),x30的數(shù)據(jù)也占8個(gè)字節(jié)
add x29, sp, #16 ; =16 ;x29 = sp + 16,x29也就是fp棧底指針
orr w8, wzr, #0x7 ;將w8置為7
stur w8, [x29, #-4] ;將w8的值存放到x29-4的位置
orr w8, wzr, #0x8 ;將w8置為8
str w8, [sp, #8] ;將w8的值存放到sp+8的位置
bl _test ;調(diào)用test函數(shù),執(zhí)行test函數(shù)的匯編代碼
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload ;調(diào)用完test,會(huì)返回到這里,繼續(xù)執(zhí)行,從sp+16的地址,加載8個(gè)字節(jié)到x29,然后再加載8個(gè)字節(jié)的數(shù)據(jù)到x30
add sp, sp, #32 ; =32 ;sp = sp + 32,回收函數(shù)的??臻g,將sp指針復(fù)原
ret ;good函數(shù)調(diào)用完畢,將x30的值給了pc寄存器,CPU會(huì)執(zhí)行pc寄存器中存放的地址所對(duì)應(yīng)的指令的
-
我們可以用一副圖,清晰的表示good函數(shù)的調(diào)用過(guò)程,的如下所示:
good函數(shù)的調(diào)用流程
-





