本文主要是講解函數(shù)的參數(shù)、返回值、局部變量在匯編中是如何存儲,以及CPSR標志寄存器

函數(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
- 如果返回值
作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS開發(fā)交流群:130595548,不管你是小白還是大牛都歡迎入駐 ,讓我們一起進步,共同發(fā)展?。ㄈ簝?nèi)會免費提供一些群主收藏的免費學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔?。?/strong>
查看系統(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
-
在sum函數(shù)中,讀取w0、w1,放入w8、w9。然后將相加后的結(jié)果放入w0(即返回值在w0寄存器)
自己優(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ù)斷住,查看匯編
以下是
viewDidLoad??臻g的存入分析過程下圖是對匯編代碼中入棧過程的一個圖示

-
以下是
test函數(shù)的匯編分析
編譯器優(yōu)化
-
debug模式改成release模式,此時再來查看匯編代碼,發(fā)現(xiàn)沒有test函數(shù),被優(yōu)化掉了
如果非要執(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ù)的返回值。(相當于將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] //保護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)系,當參數(shù)越多時,如果寄存器放不下,就需要用到內(nèi)存。就會將??臻g放大,影響??臻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ù)的匯編 -
以下是getStr函數(shù)的匯編代碼
結(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ù)匯編 -
查看
getStr函數(shù)匯編結(jié)論:發(fā)現(xiàn)
前8個參數(shù)是存儲到寄存器,第9個參數(shù)是存儲到??臻g
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ù)匯編 -
查看
getStr函數(shù)匯編- 其中
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的匯編 -
查看
funcB的匯編
總結(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沒有任何變化
-
來看funcB的匯編
- 參數(shù)入棧,其實本質(zhì)也是對參數(shù)的一個保護
- 所以使用的參數(shù)是從棧中獲取,而不是直接通過寄存器獲取
- stur 操作4個字節(jié)時使用
作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS開發(fā)交流群:130595548,不管你是小白還是大牛都歡迎入駐 ,讓我們一起進步,共同發(fā)展?。ㄈ簝?nèi)會免費提供一些群主收藏的免費學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔?。?/strong>
標記/狀態(tài)寄存器
標記/狀態(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的匯編 -
手動更改
cpsr的值(從1000 -> 0100)輸入c執(zhí)行,此時的輸出是
a == b所以,從這里可以說明,
高四位(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是保留位,主要作用是為了升級,即更新擴展
N、Z、C、V均為條件碼標志位,它們的內(nèi)容可被算術(shù)或邏輯運算的結(jié)果所改變,并且可以決定某條指令是否被執(zhí)行,意義重大!

N(Negative)標志
-
CPSR的
第31位是N,符號標志位,它記錄相關(guān)指令執(zhí)行后,其結(jié)果是否為負如果
為負,則N=1如果是
非負數(shù),則N=0
-
注意:在ARM64的指令集中,有的指令的執(zhí)行時影響狀態(tài)寄存器的。例如
adds/sub/or等,他們大都是運算指令(用于進行邏輯/算數(shù)運算)- adds 執(zhí)行add運算,且會改變標志位
案例調(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查看此時的cpsr,
高4位是6(即0110) -
執(zhí)行
adds w0, w0, #0x0:因為w0位負數(shù),加上0x0后,仍為負數(shù),所以N標志為1從cpsr的值中可以看出,6變成了8,即
0110 -> 1000,N位從0變成了1,符合我們的預(yù)期
Z(zero)標志
- CPSR的第30位是Z,0標志位,它記錄相關(guān)指令執(zhí)行后,其結(jié)果是否為0
如果
結(jié)果為0,則Z=1(此時能確定前面兩位為01,即N非負,為0,z為1)如果結(jié)果
不為0,則Z=0
對于Z的值,可以這樣看,Z標記相關(guān)指令的計算結(jié)果是否為0,如果為0,則Z要記錄下”是0“這樣的肯定信息。在計算機中1表示邏輯真,表示肯定,所以當結(jié)果為0時Z=1(表示”結(jié)果為0“)。如果結(jié)果不為0,則Z要記錄下”不是0“這樣的否定信息。在計算機中0表示邏輯假,表示否定,所以當結(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 執(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é)果為非負數(shù),所以N為0,同時也不為0,則Z為0

C(Carry)標志
CPSR的第
29位是C,進位標志位,一般情況下,進行無符號數(shù)的運算-
加法運算:當運算結(jié)果產(chǎn)生了進位時(無符號溢出),則C=1,否則C=0- 例如 1111 1111 + 1 --> 1 0000 0000,此時的1就保存在C標志位
減法運算(包括CMP):當運算時產(chǎn)生了借位時(無符號數(shù)溢出),C=0,否則C=1

* 例如 0000 0001 - 0000 0010 --> 1111 1111,
對于位數(shù)為N的無符號數(shù)來說,其對應(yīng)的二進制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相對于最高有效位的更高位,如下所示

進位
當兩個數(shù)相加時,有可能產(chǎn)生從最高有效位向更高位的進位,例如兩個32位數(shù)據(jù)0xaaaaaaaa + 0xaaaaaaaa,將產(chǎn)生進位,由于這個進位值在32位中無法保存,就說這個進位值丟失了。其實CPU在運算時,并不丟棄這個進位制,而是記錄在一個特殊的寄存器的某一位上,ARM下就用C位來記錄這個進位值,例如下面的指令
mov w0,#0xaaaaaaaa;0xa 的二進制是 1010
adds w0,w0,w0; 執(zhí)行后 相當于 1010 << 1 進位1(無符號溢出) 所以C標記 為 1
adds w0,w0,w0; 執(zhí)行后 相當于 0101 << 1 進位0(無符號沒溢出) 所以C標記 為 0
adds w0,w0,w0; 重復(fù)上面操作
adds w0,w0,w0
-
首先將
CPSR變成0x00000000,然后查看執(zhí)行mov w0,#0xaaaaaaaa后的CPSR -
執(zhí)行第一次
adds w0,w0,w0:進位1,C為1 -
執(zhí)行第二次
adds w0,w0,w0:無進位,C由1變成0 -
執(zhí)行第三次
adds w0,w0,w0:有進位,0變成1 -
執(zhí)行第四次
adds w0,w0,w0,無進位,1變成0
借位
當兩個數(shù)據(jù)做減法時,有可能向更高位借位,例如,兩個32位數(shù)據(jù)0x00000000 - 0x000000ff,將產(chǎn)生借位,借位后,相當于計算0x100000000 - 0x000000ff,得到0xffffff01這個值,由于借了一位,所以C位用來標記借位。C=0,例如下面的指令
mov w0,#0x0
subs w0,w0,#0xff
subs w0,w0,#0xff
subs w0,w0,#0xff
-
將CPSR修改為
0x00000000,執(zhí)行mov w0,#0x0 -
執(zhí)行第一次
subs w0,w0,#0xff:產(chǎn)生了借位,所以C=0 -
執(zhí)行第二次
subs w0,w0,#0xff:無借位,所以C=1 -
執(zhí)行第三次
subs w0,w0,#0xff:無借位,所以C=1
作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要,這是一個我的iOS開發(fā)交流群:130595548,不管你是小白還是大牛都歡迎入駐 ,讓我們一起進步,共同發(fā)展?。ㄈ簝?nèi)會免費提供一些群主收藏的免費學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔?。?/strong>
總結(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寄存器入棧保護-
狀態(tài)(標志)寄存器 - CPSR
arm64中
cpsr寄存器(32位)為狀態(tài)寄存器-
最高4位(28、29、30、31)為標志位
-
N標志(負標記位)執(zhí)行結(jié)果為
負數(shù)N=1執(zhí)行結(jié)果
非負數(shù)N=0
-
Z標志(0標記位)結(jié)果
為0則Z=1結(jié)果
非0則Z=0
-
C標志(無符號溢出)加法:
進位 C=1,否則C=0減法:
借位 C=0,否則C=1
-
V標志(有符號溢出)正數(shù)+正數(shù)=
負數(shù),則V=1正數(shù)+負數(shù)=
正數(shù),則V=0
-































