這里主要介紹 iOS 平臺(tái)上 ARM64 架構(gòu)的寄存器和指令以及內(nèi)存堆棧十厢。
堆棧
在 iOS 中,椢嫫耄空間是向下生長(zhǎng)的蛮放,也就是從高地址向低地址生長(zhǎng),棧頂在低地址奠宜,棧底在高地址包颁,比如:
棧頂?shù)刂罚?x00
棧底地址:0x1C
寄存器
ARM64 主要有34個(gè)寄存器,其中包括 31 個(gè)通用寄存器(x0-x30)以及 SP挎塌,PC徘六,CPSR,可以通過(guò)在調(diào)試時(shí)輸入 register read 命令查看
寄存器是64位的榴都,可以使用W0-W30來(lái)訪問(wèn)前32位空間
x0-x7
主要用來(lái)存放函數(shù)傳遞的參數(shù)待锈,以及臨時(shí)變量,超過(guò)8個(gè)參數(shù)的存放在棧上嘴高,其中 x0 也用來(lái)存放函數(shù)的返回值
FP(x29)
通常存放棧幀地址(棧底指針)竿音,指向當(dāng)前函數(shù)棧的底部
LR(x30)
通常稱(chēng) x30 為程序鏈接寄存器,因?yàn)檫@個(gè)寄存器會(huì)記錄著當(dāng)前方法的調(diào)用方地址拴驮,也就是當(dāng)前方法返回后程序繼續(xù)執(zhí)行的下一條指令地址
SP
棧頂指針春瞬,指向當(dāng)前函數(shù)棧的頂部
PC
程序計(jì)數(shù)器,指向即將要執(zhí)行的下一條指令地址套啤,軟件無(wú)法修改
CPSR
狀態(tài)寄存器宽气,有些指令會(huì)修改 CPSR 的值,比如 CPM潜沦,有些指令會(huì)根據(jù) CPSR 的值來(lái)做跳轉(zhuǎn)操作萄涯。
CPSR 的每一位值都有特殊的意義,其中最常見(jiàn)的是 NZCV 標(biāo)志位
NZCV是狀態(tài)寄存器的條件標(biāo)志位唆鸡,分別代表運(yùn)算過(guò)程中產(chǎn)生的狀態(tài)涝影,其中:
N, negative condition flag,一般代表運(yùn)算結(jié)果是負(fù)數(shù)
Z, zero condition flag, 指令結(jié)果為0時(shí)Z=1争占,否則Z=0燃逻;
C, carry condition flag, 無(wú)符號(hào)運(yùn)算有溢出時(shí),C=1臂痕。
V, oVerflow condition flag 有符號(hào)運(yùn)算有溢出時(shí)伯襟,V=1。
常用指令
常用指令可大致分為運(yùn)算指令握童,尋址指令逗旁,跳轉(zhuǎn)指令等
運(yùn)算指令
mov x1,x0 ;傳送:x1 = x0
add x0,x1,x2 ;加法:x0 = x1 + x2
sub x0,x1,x2 ;減法:x0 = x1 - x2
mul x0,x1,x2. ;乘法:x0 = x1 * x2
and x0,x0,#0xF ;位與:x0 = x0 & 0xF
orr x0,x0,#9 ;位或:x0 = x0 | 9
eor x0,x0,#0xF ;異或:x0 = x0 ^ 0xF;
lsl x0,#1 ;邏輯左移:x0 = x0 << 1
lsr x0,#1 ;邏輯右移:x0 = x0 >> 1,左邊統(tǒng)一補(bǔ)0
asr x0,#1 ;算術(shù)右移:x0 = x0 >> 1,左邊補(bǔ)符號(hào)位
尋址指令
分為兩種片效,存和取
L 打頭的基本都是取值指令红伦,如 LDR(Load Register)、LDP(Load Pair)
S 打頭的基本都是存值指令淀衣,如 STR(Store Register)昙读、STP(Store Pair)
ldr x0,[x1] ;從 x1 指向的地址里面取出一個(gè)64位大小的數(shù)存入x0
ldp x1,x2,[x10, #0x10] ;從 x10+0x10 指向的地址里面取出2個(gè)64位的數(shù),分別存入x1膨桥、x2
str x5,[sp, #24] ;往內(nèi)存中寫(xiě)數(shù)據(jù)(偏移值為正), 把 x5 的值(64位的數(shù)值)存到 sp+24 指向的地址內(nèi)存上
stur w0,[x29, #0x8] ;往內(nèi)存中寫(xiě)數(shù)據(jù)(偏移值為負(fù))蛮浑,將 w0 的值存儲(chǔ)到 x29 - 0x8 這個(gè)地址里
stp x29,x30,[sp, #-16]! ;把 x29、x30 的值存到 sp-16 的地址上只嚣,并且把sp-=16 Note:后面有個(gè)感嘆號(hào)的沮稚,然后沒(méi)有stup這個(gè)指令哈
ldp x29,x30,[sp],#16 ;從 sp 地址取出16 byte數(shù)據(jù),分別存入x29册舞、x30,然后 sp+=16
跳轉(zhuǎn)指令
bl/b bl 是有返回的跳轉(zhuǎn)蕴掏;b 是無(wú)返回的跳轉(zhuǎn),BL的L也可以理解為L(zhǎng)r
跳轉(zhuǎn)指令可以配合條件狀態(tài)(根據(jù) CPSR 相應(yīng)位置的值)
B ;跳轉(zhuǎn)指令调鲸,可帶條件跳轉(zhuǎn)與cmp配合使用
BL ;帶返回的跳轉(zhuǎn)指令盛杰, 返回地址保存到LR(X30)
BLR ; 帶返回的跳轉(zhuǎn)指令,跳轉(zhuǎn)到指令后邊跟隨寄存器中保存的地址(例:blr x8 ;跳轉(zhuǎn)到x8保存的地址中去執(zhí)行)
RET ;子程序返回指令藐石,返回地址默認(rèn)保存在LR(X30)
比較指令
cmp x1,x0 ;將x1和x0(可以是立即數(shù))的值相減即供,并根據(jù)結(jié)果設(shè)置 CPSR 標(biāo)志位
CBZ ;比較(Compare),如果結(jié)果為零(Zero)就轉(zhuǎn)移(只能跳到后面的指令)
CBNZ ;比較于微,如果結(jié)果非零(Non Zero)就轉(zhuǎn)移(只能跳到后面的指令)
入門(mén)Demo
以一個(gè)簡(jiǎn)單的 C 程序?yàn)槔旱眨缦麓a:
int sum(int a, int b)
{
int c = a + b + 1;
return c;
}
int main()
{
int a = 10;
int b = 20;
int rs = sum(a,b);
if (rs > 30) {
return 0;
}
else {
return 1;
}
}
有2個(gè)函數(shù),main 函數(shù)和 sum 函數(shù)株依,通過(guò) clang 命令成匯編代碼
clang -arch arm64 -S testAsm.c
最后生成的匯編代碼如下:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 4
.globl _sum ; -- Begin function sum
.p2align 2
_sum: ; @sum
.cfi_startproc
; %bb.0:
sub sp, sp, #16 ; =16
.cfi_def_cfa_offset 16
str w0, [sp, #12]
str w1, [sp, #8]
ldr w8, [sp, #12]
ldr w9, [sp, #8]
add w8, w8, w9
add w8, w8, #1 ; =1
str w8, [sp, #4]
ldr w0, [sp, #4]
add sp, sp, #16 ; =16
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #32 ; =32
stp x29, x30, [sp, #16] ; 16-byte Folded Spill
add x29, sp, #16 ; =16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
stur wzr, [x29, #-4]
mov w8, #10
str w8, [sp, #8]
mov w8, #20
str w8, [sp, #4]
ldr w0, [sp, #8]
ldr w1, [sp, #4]
bl _sum
str w0, [sp]
ldr w8, [sp]
cmp w8, #30 ; =30
b.le LBB1_2
; %bb.1:
stur wzr, [x29, #-4]
b LBB1_3
LBB1_2:
mov w8, #1
stur w8, [x29, #-4]
LBB1_3:
ldur w0, [x29, #-4]
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
add sp, sp, #32 ; =32
ret
.cfi_endproc
; -- End function
.subsections_via_symbols
先看 sum 函數(shù)的代碼(簡(jiǎn)單起見(jiàn)祸穷,這里去除一些不(看)重(不)要(懂)的代碼)
_sum: ; sum 函數(shù)入口
sub sp, sp, #16 ; sp = sp-16,棧頂指針向下移16字節(jié)勺三,相當(dāng)于分配了16個(gè)字節(jié)的棧空間(iOS中需曾,棧是向下(低地址)生長(zhǎng)的)
str w0, [sp, #12] ; 將 w0(x0的低32位) 存放到 s p12的位置(參數(shù)a)
str w1, [sp, #8] ; 將 w1(x1的低32位) 存放到 sp+8 的位置(參數(shù)b)
ldr w8, [sp, #12] ; 將 sp+12 位置的值讀到 w8 上吗坚,也就是 w8=a
ldr w9, [sp, #8] ; 將 sp+8 位置的值讀到 w9 上,也就是 w9=b
add w8, w8, w9 ; 兩數(shù)相加 w8 = w8 + w9
add w8, w8, #1 ; w8 = w8 + 1
str w8, [sp, #4] ; 將 w8 保存到 sp+4 的位置
ldr w0, [sp, #4] ; 將 sp+4 的位置讀到 w0 上(前面說(shuō)過(guò),x0也用來(lái)存放函數(shù)返回值)
add sp, sp, #16 ; 將棧頂指針向上移16字節(jié)(恢復(fù)棧指針呆万,相當(dāng)于釋放堆棧)
ret ; 函數(shù)返回
總體上邏輯比較簡(jiǎn)單商源,基本上沒(méi)什么難懂的地方
這里有個(gè)不明白的地方就是
str w8, [sp, #4] ldr w0, [sp, #4]
為什么不直接
mov w0, w8
有知道的大佬還請(qǐng)指教一下,為了寄存器內(nèi)容可以恢復(fù)么谋减?
再來(lái)看 main 函數(shù)的牡彻,同樣去除一些不重要的代碼
_main: ; main函數(shù)入口
sub sp, sp, #32 ; sp = sp - 32,將棧頂指針向下移動(dòng) 32 字節(jié)
stp x29, x30, [sp, #16] ; 將 x29,x30 存儲(chǔ)到 sp+16 的位置(x29,x30 參考前面介紹庄吼,這里相當(dāng)于做個(gè)備份缎除,因?yàn)楹竺嬉薷倪@2個(gè)寄存器)
add x29, sp, #16 ; x29 = sp + 16(相當(dāng)于移動(dòng)棧底指針,堆棧大小 16 字節(jié))
stur wzr, [x29, #-4] ; 將 wzr(也叫零寄存器总寻,這是一個(gè)特殊的寄存器器罐,值為0)存儲(chǔ)到 x29 - 4 的位置,相當(dāng)于把這快內(nèi)存清0
mov w8, #10 ; 將 w8 置為 10
str w8, [sp, #8] ; 將 w8 存儲(chǔ)到 sp+8 的位置
mov w8, #20 ; 將 w8 置為 20
str w8, [sp, #4] ; 將 w8 存儲(chǔ)到 sp+4 的位置
ldr w0, [sp, #8] ; 將 sp+8 讀到 w0(w0 = 10)
ldr w1, [sp, #4] ; 將 sp+4 讀到 w1 (w1 = 20渐行,前面說(shuō)過(guò)x0-x7是存放參數(shù)的)
bl _sum ; 跳轉(zhuǎn)到 sum 函數(shù)
str w0, [sp] ; 存儲(chǔ)函數(shù)返回值(w0)到 sp
ldr w8, [sp] ; 讀取 sp 的值到 w8
cmp w8, #30 ; 將 w8 的內(nèi)容和30比較轰坊,同時(shí)設(shè)置 CPSR 寄存器
b.le LBB1_2 ; 根據(jù) CPSR 結(jié)果來(lái)決定是跳轉(zhuǎn) LBB1_2(其中條件le的意思是小于等于的意思,具體可以參考我下面的鏈接)
stur wzr, [x29, #-4] ; 將 x29-4 位置內(nèi)存清0
b LBB1_3 ; 跳轉(zhuǎn)到 LBB1_3
LBB1_2:
mov w8, #1 ; 將 w8 置 1
stur w8, [x29, #-4] ; 將 w8 保存到 x29-4 的位置
LBB1_3:
ldur w0, [x29, #-4] ; 讀取 x29-4 位置的值到 w0 作為函數(shù)返回值
ldp x29, x30, [sp, #16] ; 將 sp+16 位置的值讀取到 x29,x30(恢復(fù)x29,x30)
add sp, sp, #32 ; sp = sp + 32祟印,恢復(fù)棧頂指針肴沫,釋放堆棧
ret ; 函數(shù)返回
main 函數(shù)和 sum 函數(shù)的分析基本就到這里,基本邏輯還是比較簡(jiǎn)單的蕴忆,這里有個(gè)最大的區(qū)別就是 main 函數(shù)有 x29,x30 的備份&恢復(fù)操作颤芬,原因是 sum 函數(shù)是葉子函數(shù),而 main 函數(shù)是非葉子函數(shù)孽文,非葉子函數(shù)有對(duì)其他函數(shù)的調(diào)用驻襟,需要開(kāi)辟新的堆棧空間芋哭,以及其他函數(shù)返回后需要繼續(xù)執(zhí)行下一條指令沉衣,這需要修改 x29,x30的值,所以要先備份起來(lái)减牺,而葉子函數(shù)就不需要豌习。
參考
ARM64 匯編基礎(chǔ)
iOS開(kāi)發(fā)同學(xué)的arm64匯編入門(mén)
iOS 常用匯編指令集