一、寄存器
對於日常使用高級語言的程序員來說,其看到的是連續的主存(內存)空間,使用的也是主存儲器(簡稱內存)的空間。但是對於使用匯編的程序原來說,其面對的主要是寄存器。事實上,CPU內部也是分爲運算器、控制器、寄存器、多級緩存。對於基於彙編語言的程序開發來說,寄存器是必須瞭解的一個概念。
二、通用寄存器
通用原來寄存器命名是r[0]、r[1]、r[2]、r[3]......,但是隨着armv8,也就是64位的處理器推出,寄存器命名修改爲x[n],但是爲了保持兼容,x[n]的低32位被命名爲w[n]
通用寄存器從名字上看就是做一些通用得工作的寄存器,通用工作有哪些呢?一般就是存儲內存的地址、各種計算所需的臨時數據、堆棧地址等。
2.0 棧概述
由於寄存器與棧這種數據結構有密切的關係,並且在後面也會詳細講到函數調用堆棧,爲使後面順利講述,在這裏先概要介紹一下棧。
棧是一種先進後出的數據結構。在日常生活中能夠見到的典型棧類型的東西就是——箱子,先裝進箱子的東西只能後拿出來,而後裝進去的東西則可以先拿出來。
注意
在ARM64彙編中,棧是從高地址向低地址生長的、先開劈棧空間然後再使用。這句話可能暫時不理解,但是一定請先記住。
_addwwj: ; @addwwj
Lfunc_begin0:
sub sp, sp, #16 ; 開闢棧空間
str w0, [sp, #12]
str w1, [sp, #8]
Ltmp1:
ldr w8, [sp, #12]
ldr w9, [sp, #8]
add w0, w8, w9
add sp, sp, #16 ; 回收棧空間
ret
Ltmp2:
Lfunc_end0:
在上面彙編代碼中(sub sp, sp, #16),sub是減的意思,由於棧空間是向低地址增長的,那麼所以在當前棧底的基礎之上再擴展16個字節的空間。
在上面彙編代碼中(add sp, sp, #16),把棧底又加16個字節,所以棧又恢復到初始調用此函數的狀態。
2.1 傳參寄存器
有人說xo-x7,這8個寄存器一般用來存儲函數的入參,多於8個的參數會存放在內存的之中。但是經過我的測試,發現其與優化等級相關,如果不優化(o0)的情況下,多達18個參數的情況下依然會通過寄存器來傳參,用於測試的電腦是:MacBook Pro (13-inch, M1, 2020)。如果選擇(o3)級別的優化,在參數太多的情況下會通過內存來傳遞。
總的來說,前幾個寄存器一般用於傳遞函數的參數。
2.2 x0寄存器
對於x0(w0)寄存器來說,其還有一個特殊用途,就是用來存儲函數的返回值,如下圖所示。
圖中左邊是一個返回10的函數,右邊是對應的彙編代碼,可見直接把16進制的10複製到w0寄存器來實現傳遞返回值。
2.3 fp/x29
fp(frame pointer)寄存器是x29寄存器的簡稱,用於存儲函數調用堆棧的棧底信息。
2.4 lr/x30
lr寄存器用來保存函數返回的路。
int aTestFun(int para1) {
return para1 + 3;
}
int theOtherTestFun(int para1) {
return para1 + aTestFun(para1);
}
;theOtherTestFun 對應的彙編代碼如下
0x100003f20 <+0>: sub sp, sp, #0x20 ; =0x20
0x100003f24 <+4>: stp x29, x30, [sp, #0x10]
0x100003f28 <+8>: add x29, sp, #0x10 ; =0x10
0x100003f2c <+12>: stur w0, [x29, #-0x4]
-> 0x100003f30 <+16>: ldur w8, [x29, #-0x4]
0x100003f34 <+20>: ldur w0, [x29, #-0x4]
0x100003f38 <+24>: str w8, [sp, #0x8]
0x100003f3c <+28>: bl 0x100003f08 ; aTestFun at CImp.c:10 這裏是調用 aTestFun函數,進入此函數之後,lr的地址是0x100003f40
0x100003f40 <+32>: ldr w8, [sp, #0x8] ; 就是這個地址
0x100003f44 <+36>: add w0, w8, w0
0x100003f48 <+40>: ldp x29, x30, [sp, #0x10]
0x100003f4c <+44>: add sp, sp, #0x20 ; =0x20
0x100003f50 <+48>: ret
在上面的截圖中,函數theOtherTestFun調用aTestFun函數,當從aTestFun函數返回是需要執行theOtherTestFun中調用aTestFun函數的下一行。
0x100003f3c <+28>: bl 0x100003f08 ; aTestFun at CImp.c:10 實現調用aTestFun函數
0x100003f40 <+32>: ldr w8, [sp, #0x8] ; aTestFun返回後,需要執行的下語句彙編代碼
;從上面的截圖來看,當前lr地址就是上面彙編指令的地址
2.5 sp/x31
sp(stack pointer)寄存器是寄存器x31的簡稱,用於存儲函數調用堆棧的棧頂信息。
int need1Parameter(int para1) {
return 10;
}
; 這裏就是上述 need1Parameter 函數的彙編代碼
0x100003dac <+0>: sub sp, sp, #0x10 ; =0x10 此時移動棧頂空間,爲need1Parameter函數開闢堆棧空間
0x100003db0 <+4>: str w0, [sp, #0xc]
0x100003db4 <+8>: mov w0, #0xa
-> 0x100003db8 <+12>: add sp, sp, #0x10 ; =0x10 need1Parameter函數執行完畢,回收剛纔開闢的堆棧空間
0x100003dbc <+16>: ret
2.6 pc/x32
所有的數據、指令、常量等,在內存中,都是01001。那麼,cpu怎麼知道哪段01序列是命令,需要CPU來執行呢?其實很簡單,pc寄存器所指的內容就會被當做指令。
2.7 cpsr/x33
目前,上面介紹的寄存器都是作爲一個整體來使用的,但是cpsr寄存器是每個位都單獨來使用的。其標識當前程序所處的狀態,目前暫未涉及,先不做深入介紹,如果想要深入理解的話,可以自行百度。
三、浮點數寄存器
爲了加速浮點數的處理,現在arm提供專用的浮點數寄存器。64位的浮點數寄存器是V0-V31、spsr、spcr。
四、向量寄存器
爲了支持遊戲等用到向量計算比較多的情況,CPU針對向量計算,提供了向量寄存器。
五、寄存器相關lldb命令
- register read 讀取所有通用寄存器
- register read x0 讀取某個寄存器
- register write x0 0x0001 向寄存器寫入值
注意
例如,pc寄存器是不能直接寫入值得。