本文主要是講解函數(shù)的參數(shù)
、返回值
、局部變量
在匯編中是如何存儲,以及CPSR
標(biāo)志寄存器
函數(shù)的參數(shù)和返回值
-
arm64下,函數(shù)的
參數(shù)
是存放在x0-x7(w0-w7)
這8個寄存器里面的,如果超過8個參數(shù),就會入棧如果自定義函數(shù)時,
參數(shù)最好不要超過6個
(因為有兩個隱藏參數(shù)self,_cmd
)如果函數(shù)需要多個參數(shù),可以傳入數(shù)組、結(jié)構(gòu)體、指針等類型
-
函數(shù)的
返回值
放在x0寄存器
中- 如果返回值
大于8個
字節(jié),就會利用內(nèi)存?zhèn)鬟f
- 如果返回值
查看系統(tǒng)的參數(shù)匯編
下面通過系統(tǒng)中對函數(shù)的匯編來查看系統(tǒng)對參數(shù)、返回值是如何操作的
int sum(int a, int b){
return a + b;
}
- (void)viewDidLoad{
[super viewDidLoad];
sum(10, 20);
}
-
查看匯編,在跳轉(zhuǎn)到sum函數(shù)之前,已經(jīng)將參數(shù)存入了w0、w1
系統(tǒng)的參數(shù)匯編-01 -
在sum函數(shù)中,讀取w0、w1,放入w8、w9。然后將相加后的結(jié)果放入w0(即返回值在w0寄存器)
系統(tǒng)的參數(shù)匯編-02
自己優(yōu)化實現(xiàn)suma
運行發(fā)現(xiàn),其結(jié)果與sum函數(shù)是一致的,結(jié)果都是30
<!--asm.s-->
.text
.global _suma
_suma:
add x0, x0, x1
ret
<!--調(diào)用-->
int suma;
- (void)viewDidLoad{
[super viewDidLoad];
suma(10, 20);
}
編譯器優(yōu)化
來看以下代碼的匯編
int test(int a, int b, int c, int d, int e, int f, int g, int h, int i){
return a + b + c + d + e + f + g + h + i;
}
- (void)viewDidLoad {
[super viewDidLoad];
test(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
-
在test函數(shù)斷住,查看匯編
未優(yōu)化分析-01
以下是viewDidLoad
棧空間的存入分析過程
未優(yōu)化分析-02
下圖是對匯編代碼中入棧過程的一個圖示
未優(yōu)化分析-03 -
以下是
test
函數(shù)的匯編分析
未優(yōu)化分析-04
編譯器優(yōu)化
-
debug模式改成release模式,此時再來查看匯編代碼,發(fā)現(xiàn)沒有test函數(shù),被優(yōu)化掉了
優(yōu)化分析-01 如果非要執(zhí)行test,可以這樣寫
- (void)viewDidLoad {
[super viewDidLoad];
printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9));
}
匯編代碼如下,發(fā)現(xiàn)優(yōu)化后的test函數(shù)在匯編中,其本質(zhì)是一個數(shù)
,也就是test函數(shù)的返回值
。(相當(dāng)于將printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9));
直接優(yōu)化成了printf("%d", 45);
)
通過匯編實現(xiàn)函數(shù)
- 定義函數(shù)聲明及調(diào)用
int funcA(int a, int b);
- (void)viewDidLoad {
[super viewDidLoad];
int a = funcA(10, 20);
printf("%d", a);
}
- 匯編實現(xiàn)
funcA
.text
.global _funcA, _sum
_funcA:
sub sp, sp, #0x10
stp x29, x30, [sp] //保護(hù)lr
bl _sum
ldp x29, x30, [sp]
add sp, sp, #0x10
ret
_sum:
add x0, x0, x1
ret
<!--簡寫-->
_funcA:
stp x29, x30, [sp, #-0x10]!
bl _sum
ldp x29, x30, [sp], #0x10
ret
_sum:
add x0, x0, x1
ret
<!--巧合:還可以將bl替換成b-->
//b就是簡單跳轉(zhuǎn),在逆向中用于繞過某些代碼(例如安全監(jiān)測)
_funcA:
b _sum
_sum:
add x0, x0, x1
ret
運行結(jié)果如下所示
說明:
- 關(guān)于b指令:只是跳轉(zhuǎn),不改變lr寄存器
- 拉伸??臻g和參數(shù)個數(shù)有沒有關(guān)系?:有關(guān)系,當(dāng)參數(shù)越多時,如果寄存器放不下,就需要用到內(nèi)存。就會將棧空間放大,影響??臻g的不僅僅是參數(shù)個數(shù),還有局部變量
函數(shù)的返回值
如果返回值是一個結(jié)構(gòu)體類型,一個寄存器放不下,這時是什么情況?
有以下代碼,運行查看其匯編
struct str {
int a;
int b;
int c;
int d;
int f;
int g;
};
struct Str getStr(int a, int b, int c, int d, int f, int g){
struct Str str1;
str1.a = a;
str1.b = b;
str1.c = c;
str1.d = d;
str1.f = f;
str1.g = g;
return str1;
}
- (void)viewDidLoad {
[super viewDidLoad];
struct Str str2 = getStr(1, 2, 3, 4, 5, 6);
}
- 斷點運行,以下是
viewDidLoad
函數(shù)的匯編
返回值調(diào)試-01 -
以下是getStr函數(shù)的匯編代碼
返回值調(diào)試-02
結(jié)論:如果返回值大于x0的8個字節(jié)
,也會使用??臻g
來存儲
練習(xí)
1、如果函數(shù)的參數(shù)/返回是9個呢?
struct Str{
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
};
struct Str getStr(int a, int b, int c, int d, int e, int f, int g, int h, int i){
struct Str str1;
str1.a = a;
str1.b = b;
str1.c = c;
str1.d = d;
str1.e = e;
str1.f = f;
str1.g = g;
str1.h = h;
str1.i = i;
return str1;
}
- (void)viewDidLoad {
[super viewDidLoad];
struct Str str2 = getStr(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
-
查看
viewDidLoad
函數(shù)匯編
返回值練習(xí)-01 -
查看
getStr
函數(shù)匯編
返回值練習(xí)-02
結(jié)論:發(fā)現(xiàn)前8
個參數(shù)是存儲到寄存器
,第9
個參數(shù)是存儲到棧空間
2、如果結(jié)構(gòu)體參數(shù)是10個呢?
struct Str{
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
int j;
};
struct Str getStr(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){
struct Str str1;
str1.a = a;
str1.b = b;
str1.c = c;
str1.d = d;
str1.e = e;
str1.f = f;
str1.g = g;
str1.h = h;
str1.i = i;
str1.j = j;
return str1;
}
- (void)viewDidLoad {
[super viewDidLoad];
struct Str str2 = getStr(1, 2, 3, 4, 5, 6, 7, 8, 9);
-
查看
viewDidLoad
函數(shù)匯編
返回值練習(xí)-03 -
查看
getStr
函數(shù)匯編
返回值練習(xí)-04- 其中
w0-w7
都是參數(shù) -
x8
用于返回值參照 -
w9
用作臨時變量
- 其中
結(jié)論:前8個參數(shù)存儲到寄存器,后兩個參數(shù)存儲到??臻g
函數(shù)的局部變量
- 函數(shù)的
局部變量
放在棧
里面
分析下面代碼的匯編
int funcB(int a, int b){
int c = 6;
return a + b + c;
}
- (void)viewDidLoad {
[super viewDidLoad];
funcB(10, 20);
}
-
查看
viewDidLoad
的匯編
局部變量-01 -
查看
funcB
的匯編
局部變量-02
總結(jié):
-
局部變量
存儲在棧
空間 - 參數(shù)的傳遞是用的寄存器,然后將寄存器的值寫入棧
如果函數(shù)有嵌套調(diào)用的情況呢?
int funcB(int a, int b){
int c = 6;
int d = funcSum(a, b, c);
return d;
}
int funcSum(int a, int b, int c){
int d = a + b + c;
printf("%d", d);
return d;
}
- (void)viewDidLoad {
[super viewDidLoad];
funcB(10, 20);
}
-
從viewDidLoad到funcB沒有任何變化
函數(shù)嵌套-01 -
來看funcB的匯編
函數(shù)嵌套-02- 參數(shù)入棧,其實本質(zhì)也是對參數(shù)的一個保護(hù)
- 所以使用的參數(shù)是從棧中獲取,而不是直接通過寄存器獲取
- stur 操作4個字節(jié)時使用
標(biāo)記/狀態(tài)寄存器
標(biāo)記/狀態(tài)寄存器:主要用于控制程序的執(zhí)行流程
引子
分析下面函數(shù)的匯編
void func(){
int a = 1;
int b = 2;
if (a == b){
printf("a == b");
}else{
printf("error");
}
}
- (void)viewDidLoad {
[super viewDidLoad];
func();
}
- 查看
func
的匯編
引子-01 - 手動更改
cpsr
的值(從1000 -> 0100)
引子-02
輸入c執(zhí)行,此時的輸出是a == b
引子-03
所以,從這里可以說明,高四位(31-28)是有特殊含義的
CPSR
CPU內(nèi)部的寄存器中,有一種特殊的寄存器(對于不同的處理器,個數(shù)和數(shù)據(jù)結(jié)構(gòu)都可能不同)。這種寄存器在ARM中,被稱為
狀態(tài)寄存器
,即CPSR(current program status register)寄存器
CPSR和其他寄存器不一樣,其他寄存器是用來存放數(shù)據(jù)的,都是整個寄存器具有一個含義,而CPSR寄存器是按位起作用的,即它的
每一位都有專門的含義,用于記錄特定的信息
注意:
CPSR
寄存器是32位
的CPSR的
低8位(包括I、F、T和M[4:0])
稱為控制位
,程序無法修改
,除非CPU運行于特權(quán)模式下,程序才能修改控制位中間27-8是保留位,主要作用是為了升級,即更新擴(kuò)展
-
N、Z、C、V
均為條件碼標(biāo)志位
,它們的內(nèi)容可被算術(shù)或邏輯運算的結(jié)果所改變
,并且可以決定某條指令是否被執(zhí)行,意義重大!
CPSR圖示
N(Negative)標(biāo)志
- CPSR的
第31位是N
,符號標(biāo)志位
,它記錄相關(guān)指令執(zhí)行后,其結(jié)果是否為負(fù)如果
為負(fù)
,則N=1
如果是
非負(fù)數(shù)
,則N=0
- 注意:在ARM64的指令集中,有的指令的執(zhí)行時影響狀態(tài)寄存器的。例如
adds/sub/or
等,他們大都是運算指令(用于進(jìn)行邏輯/算數(shù)運算)- adds 執(zhí)行add運算,且會改變標(biāo)志位
案例調(diào)試
查看以下代碼的匯編
void func(){
asm(
"mov w0, #0xffffffff\n"
"adds w0, w0, #0x0\n"
);
}
- (void)viewDidLoad {
[super viewDidLoad];
func();
}
-
執(zhí)行
mov w0, #0xffffffff
:w0賦值-1
N調(diào)試-01
查看此時的cpsr,高4位是6(即0110)
N調(diào)試-02 -
執(zhí)行
adds w0, w0, #0x0
:因為w0位負(fù)數(shù),加上0x0后,仍為負(fù)數(shù),所以N標(biāo)志為1
N調(diào)試-03
從cpsr的值中可以看出,6變成了8,即0110 -> 1000
,N
位從0變成了1
,符合我們的預(yù)期
Z(zero)標(biāo)志
- CPSR的第30位是Z,0標(biāo)志位,它記錄相關(guān)指令執(zhí)行后,其結(jié)果是否為0
如果
結(jié)果為0
,則Z=1
(此時能確定前面兩位為01,即N非負(fù),為0,z為1)如果結(jié)果
不為0
,則Z=0
對于Z的值,可以這樣看,Z標(biāo)記相關(guān)指令的計算結(jié)果是否為0,如果為0,則Z要記錄下”是0“
這樣的肯定信息。在計算機(jī)中1表示邏輯真,表示肯定,所以當(dāng)結(jié)果為0時Z=1
(表示”結(jié)果為0“)。如果結(jié)果不為0,
則Z要記錄下”不是0“
這樣的否定信息。在計算機(jī)中0表示邏輯假,表示否定,所以當(dāng)結(jié)果不為0時Z=0,表示”結(jié)果不為0“
案例調(diào)試
目的:驗證z為1時,N必為0
void func(){
asm(
"mov w0, #0x0\n"
"adds w0, w0, #0x0\n"
);
}
-
查看此時的
CPSR
Z調(diào)試-01 執(zhí)行
mov w0, #0x0
和adds w0, w0, #0x0
,發(fā)現(xiàn)N和Z仍然是0和1修改:將adds中的0x0更改為0x1
void func(){
asm(
"mov w0, #0x0\n"
"adds w0, w0, #0x1\n"
);
}
查看CPSR,從圖中可以看出,由于為結(jié)果為非負(fù)數(shù)
,所以N為0
,同時也不為0,則Z為0
C(Carry)標(biāo)志
CPSR的第
29
位是C
,進(jìn)位標(biāo)志位
,一般情況下,進(jìn)行無符號數(shù)的運算
-
加法
運算:當(dāng)運算結(jié)果產(chǎn)生了進(jìn)位時
(無符號溢出),則C=1
,否則C=0
- 例如 1111 1111 + 1 --> 1 0000 0000,此時的1就保存在C標(biāo)志位
-
減法運算(包括CMP)
:當(dāng)運算時產(chǎn)生了借位時
(無符號數(shù)溢出),C=0
,否則C=1
C圖示- 例如 0000 0001 - 0000 0010 --> 1111 1111,
對于位數(shù)為N的無符號數(shù)來說,其對應(yīng)的二進(jìn)制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相對于最高有效位的更高位,如下所示
進(jìn)位
當(dāng)兩個數(shù)相加時,有可能產(chǎn)生從最高有效位向更高位的進(jìn)位,例如兩個32位數(shù)據(jù)0xaaaaaaaa + 0xaaaaaaaa
,將產(chǎn)生進(jìn)位,由于這個進(jìn)位值在32位中無法保存,就說這個進(jìn)位值丟失了。其實CPU在運算時,并不丟棄這個進(jìn)位制,而是記錄在一個特殊的寄存器的某一位上,ARM下就用C位來記錄這個進(jìn)位值,例如下面的指令
mov w0,#0xaaaaaaaa;0xa 的二進(jìn)制是 1010
adds w0,w0,w0; 執(zhí)行后 相當(dāng)于 1010 << 1 進(jìn)位1(無符號溢出) 所以C標(biāo)記 為 1
adds w0,w0,w0; 執(zhí)行后 相當(dāng)于 0101 << 1 進(jìn)位0(無符號沒溢出) 所以C標(biāo)記 為 0
adds w0,w0,w0; 重復(fù)上面操作
adds w0,w0,w0
-
首先將
CPSR
變成0x00000000
,然后查看執(zhí)行mov w0,#0xaaaaaaaa
后的CPSR
進(jìn)位調(diào)試-01 -
執(zhí)行第一次
adds w0,w0,w0
:進(jìn)位1,C為1
進(jìn)位調(diào)試-02 -
執(zhí)行第二次
adds w0,w0,w0
:無進(jìn)位,C由1變成0
進(jìn)位調(diào)試-03 -
執(zhí)行第三次
adds w0,w0,w0
:有進(jìn)位,0變成1
進(jìn)位調(diào)試-04 -
執(zhí)行第四次
adds w0,w0,w0
,無進(jìn)位,1變成0
進(jìn)位調(diào)試-05
借位
當(dāng)兩個數(shù)據(jù)做減法時,有可能向更高位借位,例如,兩個32位數(shù)據(jù)0x00000000 - 0x000000ff
,將產(chǎn)生借位,借位后,相當(dāng)于計算0x100000000 - 0x000000ff
,得到0xffffff01
這個值,由于借了一位,所以C位用來標(biāo)記借位。C=0,例如下面的指令
mov w0,#0x0
subs w0,w0,#0xff
subs w0,w0,#0xff
subs w0,w0,#0xff
-
將CPSR修改為
0x00000000
,執(zhí)行mov w0,#0x0
借位調(diào)試-01 -
執(zhí)行第一次
subs w0,w0,#0xff
:產(chǎn)生了借位,所以C=0
借位調(diào)試-02 -
執(zhí)行第二次
subs w0,w0,#0xff
:無借位,所以C=1
借位調(diào)試-03 -
執(zhí)行第三次
subs w0,w0,#0xff
:無借位,所以C=1
借位調(diào)試-04
總結(jié)
-
函數(shù)參數(shù)
arm64中,參數(shù)是放在
x0-x7
的8個寄存器中如果是浮點數(shù),就會用浮點數(shù)寄存器
如果
超過8個
參數(shù)就會用棧傳遞
-
函數(shù)返回值
一般函數(shù)的返回值使用
x0寄存器
保存如果返回值
大于了8個字節(jié)
(x0寄存器大小是8個字節(jié)),就會利用內(nèi)存?zhèn)鬟f
返回值
-
函數(shù)局部變量
-
局部變量
存儲在棧
空間
-
函數(shù)的嵌套調(diào)用:會將
x29、x30
寄存器入棧保護(hù)-
狀態(tài)(標(biāo)志)寄存器 - CPSR
arm64中
cpsr
寄存器(32位
)為狀態(tài)寄存器-
最高4位(28、29、30、31)為標(biāo)志位
-
N
標(biāo)志(負(fù)標(biāo)記位)執(zhí)行結(jié)果為
負(fù)數(shù)N=1
執(zhí)行結(jié)果
非負(fù)數(shù)N=0
-
Z
標(biāo)志(0標(biāo)記位)結(jié)果
為0則Z=1
結(jié)果
非0則Z=0
-
C
標(biāo)志(無符號溢出)加法:
進(jìn)位 C=1,否則C=0
減法:
借位 C=0,否則C=1
-
V
標(biāo)志(有符號溢出)正數(shù)+正數(shù)=
負(fù)數(shù),則V=1
正數(shù)+負(fù)數(shù)=
正數(shù),則V=0
-