在之前,我们提到,当函数的参数小于等于4个时,我们会把它存放于寄存器中,但是如果函数参数大于4个,我们就需要通过stack去进行参数的存储,这毫无疑问,将增加代码操作的复杂性,因为我们需要对于栈利用stack pointer进行控制。
代码回顾
我们回到上次讲到的代码,实现一个加法。但是,如果需要进行6个参数的加法,那么流程会更加复杂。(R0 - R3用于存放前四个参数)
.global _start
_start:
mov r0, #1 //arg1
mov r1, #2 //arg2
push {r0, r1}
bl add_numbers
mov r2,r0
pop {r0,r1}
add_numbers:
add r0, r0, r1
bx lr
代码实现
接下来,我们来一步步实现
1. 传入参数
我们将前四个参数分别传入R0 - R3寄存器,因为涉及到function call,所以我们在LR中备份函数调用后需要返回的地址。接下来,我们在栈中预留8byte的空间,用于存放2个数字。通过str指令,我们可以将r4中的数值存放到sp所对应的地址中。
push {lr}
// save the link register,
// so my program knows where to jump back to.
mov r0, #1
mov r1, #2
mov r2, #3
mov r3, #4
//allocate space on the stack to store each variable
//In ARMv7, integer values are 4 bytes long
sub sp, sp, #8
mov r4, #6
str r4, [sp]
mov r4, #5
str r4, [sp, #4]
下图可见,在上述代码的一连串操作后,栈中存放了两个数字以及link register的地址。
2. 函数调用
在step into 函数中,我们需要基于栈和寄存器中的信息,进行加法。然后,基于lr的信息,我们回到了函数被调用的下一条指令。
add_numbers:
add r0, r0, r1
add r0, r0, r2
add r0, r0, r3
ldr r4, [sp, #4]
add r0, r0, r4
ldr r4, [sp]
add r0, r0, r4
bx lr
3. 恢复状态
在调用完add_numbers label后,我们需要更新栈,并回到被调用前的状态。
bl add_numbers
mov r2, r0
//reset the stack pointer
add sp, sp, #8
pop {lr}
//sp from -4 to 0
The stack grows downward in memory. Subtract from the stack pointer to allocate space for new values.
.global _start
_start:
push {lr}
mov r0, #1 //arg1
mov r1, #2 //arg2
mov r2, #3
mov r3, #4
//allocate space on the stack
sub sp, sp, #8
mov r4, #6
str r4, [sp]
mov r4, #5
str r4, [sp, #4]
bl add_numbers
mov r2,r0
add sp, sp, #8
pop {lr}
add_numbers:
add r0, r0, r1
add r0, r0, r2
add r0, r0, r3
ldr r4, [sp, #4]
add r0, r0, r4
ldr r4, [sp]
add r0, r0, r4
bx lr
TakeAway
STR
用于将寄存器中的数据存入memory中。
STR R0, [R1] // Store the value in R0 into the memory address in R1
BL
Function Calls: BL is primarily used for calling functions. It allows for both simple function calls and nested or recursive calls.
Return Address Storage: The address of the instruction following the BL is stored in the link register (LR). This allows the called function to return control to the caller.