一、簡介
CPU
中央處理器,內部主要包括寄存器、運算器、控制器。
- 寄存器:存儲數據
- 運算器:處理數據
- 控制器:控制硬件
IO
口的高低電平。
常用寄存器
pc:
程序計數器,確定指令位置sp:
在任意時刻都會保存棧頂的地址,調用函數就會開闢棧空間(通過操作sp寄存器
來開闢棧空間)fp:
也稱爲x29
寄存器屬於通用寄存器,在某些時刻利用它來保存棧底的地址
x30寄存器
x30寄存器
存放當前調用函數的返回地址- 當
ret
指令執行時,會找到x30寄存器
保存的地址值,繼續向下執行
常用指令
str
:讀取寄存器值,存入內存中ldr
:讀內存中的值,存入到寄存器stp
:入棧指令stp x0, x1, [sp]
存入兩個值ldp
:出棧指令ldp x0, x1, [sp]
取出兩個值bl
:將下一條指令的地址放入lr(x30)
寄存器,跳轉到標號處執行指令ret
:默認使用lr(x30)寄存器
的值,通過底層指令提示CPU
此處作爲下條指令的地址orr
:orr{條件}{S} 目的寄存器,操作數1,操作數2
,把結果放置到目的寄存器
函數參數和返回值
ARM64
下,函數存放在x0~x7(w0~w7)
這8
個寄存器中,超過8
個參數,就會入棧。函數返回值是放在x0寄存器
中的。通用寄存器32
個。
二、彙編函數嵌套
1、demo1-彙編函數嵌套
.text
.global _A, B
_A:
mov x0, #0xaaaa
bl _B
mov x0,#0xaaaa
ret
_B:
mov x0, #0xbbbb
ret
執行順序:
demo`A:
0x102c0a0c4 <+0>: mov x0, #0xaaaa
0x102c0a0c8 <+4>: bl 0x102c0a0d4 ; B ①
0x102c0a0cc <+8>: mov x0, #0xaaaa ②
-> 0x102c0a0d0 <+12>: ret
繼續執行,①和②會來回執行,①->②->①->…。
原因:在A函數
中調用了B函數
,這裏x30
的值將被置爲B函數
的結束地址,繼續執行到ret
,ret
會讀取了x30
的地址(B函數的結束地址)①處,繼續往下執行到②,因此就①->②->①->…。
下面看系統是如何處理嵌套函數的調用的:
2、demo2-c函數嵌套
int A(void);
void b() {
return;
}
void c() {
b();
}
int main(int argc, char * argv[]) {
c();
// A();
}
斷點單步執行打印如下:
demo`c:
0x102f9a318 <+0>: stp x29, x30, [sp, #-0x10]!
0x102f9a31c <+4>: mov x29, sp
0x102f9a320 <+8>: bl 0x102f9a314 ; b at main.m:14:5
0x102f9a324 <+12>: ldp x29, x30, [sp], #0x10
-> 0x102f9a328 <+16>: ret
stp:
寫入,向x29、x30
寫入到棧空間stp x29, x30, [sp, #-0x10]!:
等價於sp = sp-0x10(16字節)
並賦值所在地址,拉伸棧空間,拉伸棧空間的大小爲16
字節的倍數- 執行
c函數
ldp x29, x30, [sp], #0x10:
將用sp
所在地址值給x29、x30
賦值,sp+0x10
釋放空間,保持棧平衡
在每一步打印x30
的值:
從上面的運行結果可以看出,x30
寄存器在調起內嵌函數前,存儲x30
地址到 [sp, #-0x10]
的地址中,內嵌函數調用完成後,重新設置當前x30 = sp
(sp
存儲了當前函數的地址),執行到ret
,ret
讀取到的地址即當前函數的結束地址,繼續執行則跳出該函數。
3、demo3-完善demo1
在函數內調用函數,保存當前函數A
結束地址x30
到sp-0x10(16個字節)
位置,函數B
結束後重新設置x30
的值爲sp(函數A的結束地址)
,這樣就完成嵌套函數調用。
.text
.global _A, B
_A:
mov x0, #0xaaaa
str x30,[sp, #-0x10]!
bl _B
mov x0,#0xaaaa
ldr x30, [sp], #0x10
ret
_B:
mov x0, #0xbbbb
ret
如下:
三、函數
上面瞭解了彙編函數嵌套的處理方法,下面看一下在彙編層對參數是怎麼處理的。
int sum(int a, int b) {
return a+b;
}
int main(int argc, char * argv[]) {
Int res = sum(5,7);
}
斷點查看主函數彙編代碼:

sub sp, sp, #0x30
:sp-0x30
申請48
個字節的棧空間(sp
指向可用棧空間的棧頂),sub
減指令x29、x30
保存棧底棧頂,做爲嵌套函數的中間變量- 上面可以看到變量值
#0x5、#0x7
,存入到w0、w1
寄存器中
進入sum函數
內查看,彙編指令:

- 在
sum
函數內拉伸棧空間 str
指令將w0、w1
寄存器中的值入棧,再取出,有說法是爲防止寄存器被使用,存儲值發生變化,使用前讀取棧區的值就不會出現被串改的問題,優化後的指令是直接走add sp, sp, #0x10
的。但是既然在連續執行的指令中都有被串改的可能,那麼在取值後,add
前也是有可能被串改的,所以感覺以上說法並不能解釋這一多餘操作,除非後面指令中,有使用該參數值,存儲到棧是有必要的sp, sp, #0x10
:數據處理完成回收棧空間ret
:有參數函數返回值是x0寄存器
的值不是x30寄存器
的值,w0
是x0
寄存器的低32
位,因此x0=w0
,ret=w0=0x12=12
編譯器優化:

優化後的彙編指令:

- 優化掉了參數的存儲,取值,直接將寄存器值相加
- 沒有拉伸棧空間
掉了兩根頭髮!!!
多參數demo
int sum(int a,int b,int c,int d,int e,int f,int g,int h,int i,int j,int k,int l) {
return a+b+b+c+d+e+f+g+h+i+j+k+l;
}
int main(int argc, char * argv[]) {
int res = sum(5,7);
}
main函數
彙編指令如下:
初始化寄存器的值,這裏使用w0~w8、x9
,這裏w0=x0,w9=x9
,不用糾結爲什麼沒有都使用w
或x
,w
是x
的低32
位,同屬於一個寄存器,在系統級別怎麼用都行。過!
進入函數內部:

拉伸棧空間,存寄存器值,取值,相加,指令太多,每一條指令耗時1/主頻
,複合指令耗時2/主頻
,這麼多指令,太燒了。
局部變量
demo1-函數多參數
int funcC() {
int a = 1;
int b = 2;
int c = 3;
return a+b+c;
}
int main(int argc, char * argv[]) {
int res = funcC();
}
函數彙編指令如下:
- 開闢棧空間
0x10
- 將值存入到
w8寄存器
中(任意w) - 將寄存器值入棧,出棧,計算
再看一段代碼:
int funcC() {
return 1+2+3;
}
int main(int argc, char * argv[]) {
int res = funcC();
}
彙編指令:

這裏就執行了一條指令,其實內部有做add
相關指令,這裏做了優化,但相比上面聲明的局部變量,這裏沒有開闢棧空間,省去了很多指令,每一條指令耗時1/主頻
,複合指令耗時2/主頻
,每條指令都要放電一次,耗電,局部變量悠着點用,當然真正開發中編譯器是會優化掉這些多餘代碼。
……
……