棧、調用棧、棧幀
棧
在當今多數計算機體系架構中,函數的參數傳遞、局部變量的分配和釋放都是通過操縱棧來實現的。
Stack Frame
每次調用一個函數,都要為該次調用的函數實例分配棧空間。為單個函數分配的那部分棧空間就叫做棧幀。關于Stack Frame 更詳細的內容請查看Peeking Stack Frame
Call Stack
調用棧就是正在使用的棧空間,由多個嵌套調用函數所使用的棧幀組成。具體來說,Call Stack 就是指存放某個程序的正在運行的函數的信息的棧。Call Stack 由 Stack Frames 組成,每個 Stack Frame 對應于一個未完成運行的函數。
在內存中,棧是從高地址向低地址延伸的,即棧底對應高地址,棧頂對應低地址。
幾個重要的寄存器
- SP寄存器(x31/arm64,r11/armv7), Stack Pointer, 指向棧低的指針.
- PC寄存器(r15/armv7), Program Counter, 記錄當前執行的代碼的地址. 它是一個隱含的寄存器, 無法被直接訪問, 只能被特定的指令隱含訪問.
- LR寄存器 (x30/arm64,r14/armv7), Link Register, 指向返回地址, 即return時回到的地址.
*FP寄存器 (x29/arm64,r11/armv7), Frame Pointer, 指向上一次方法調用的frame的最高位地址, frame位于棧上。
在i386 下,對應的寄存器有:
ESP 寄存器為 Stack Pointer ,它始終指向棧頂的位置。
EIP 寄存器為返回地址,它是調用函數( Caller )在執行完 Call 指令后的下一條指令的地址。
EBP 寄存器為 Frame Pointer
Frame
frame其實就是一個按照方法調用順序, 從棧的高地址向低地址依次存放的一組數據。stack frame的兩個邊界分別由FP和SP來限定:
常用指令
b 跳轉到地址(無返回), 不會改變LR寄存器的值
bl 跳轉到地址(有返回), 會改變LR寄存器的值為返回地址
ldr/ldur 地址對應的內容加載到寄存器
str/stur 寄存器內容存儲到內存地址
cbz/cbnz 為零跳轉到地址/不為零跳轉到
add 加法運算
mov 寄存器之間內容移動
ldp/stp 從棧取/存數據
adrp, 用來定位數據段中的數據用, 因為aslr會導致代碼及數據的地址隨機化, 用adrp來根據pc做輔助定位
ldp 命令能一次獲取給兩個寄存器賦值。
add x0,x0,#1 x0 = x0 + 1
add x0,x0,#0x30 x0 = x0 + 0x30
add x0,x1,x3 x0 =x1+x3
add x0,x1,[x2] x0 =x1+[x2], 把x1的內容加上x2的內容作為地址取內存內容放入x0
ldr x0,[x1] x0 =[x1], 把x1的內容作為地址取內存內容放入x0
str x0,[x1] *x1 = x0 , 把x0的內容放入,x1的內容作為地址的內存中
ldr x0,[x1,#4] x0 =[x1+4], 把x1的內容加上4, 作為內存地址, 取其內容放入x0
ldr x0,[x1],#4 x0 =[x1] 、x1 =x1+4, 把x1的內容作為內存地址取內存內容放入x0, 并把x1的內容加上4放入x1
ldr x0,[x1,x2] x0 =[x1+x2], 把x1和x2的內容相加, 作為內存地址取內存內容放入x0
調用約定
31個 64位的通用(整數)寄存器, 在A64指令集里 全部可見,分別是r0-r30
在64位環境中, 這些寄存器 一般用x0-x30的名字
在32位環境中, 這些寄存器 使用 w0-w30的名字
相關更多信息點擊查看
在iOS 上參數傳遞的順序:self ,selector ,param....
x0 存self, 另外,x0也可以作為函數返回值時候的寄存器。
x1 selector
x2,x3,x4 參數
根據文檔5.4.2,stage B 里面說到,當參數是一個大于16 bytes 的復合類型,caller會分配內存保存參數。然后這個內存地址的值會存入寄存器x8。callee可以隨時修改x8 上的數據。
FYI
* callee: 指向正在執行的函數
* caller: 指向調用當前函數的函數
To be continue .....
Ref:
Assemble Language Function Calls
objc-msg-arm64
Procedure Call Standard for the ARM 64-bitArchitecture
ARM 64 bit mode