调用栈我们以阶乘为例。阶乘比较经典。
堆栈定义:堆栈是仅由当前动态激活拥有的内存区域。
我们先来看下阶乘的C代码如下:
int factorial(int n)
{
if (n == 0)
return 1;
else
return n * factorial(n-1);
}
.data
message1: .asciz "Type a number: "
format: .asciz "%d"
message2: .asciz "The factorial of %d is %d\n"
.text
factorial:
str lr, [sp,#-4]! /* Push lr 到堆栈。*/
str r0, [sp,#-4]! /* Push 参数r0 到堆栈,这个是函数的参数*/
cmp r0, #0 /* 对比r0 and 0 */
bne is_nonzero /* if r0 != 0 then 继续分支*/
mov r0, #1 /* 如果参数是0,则r0 ← 1,函数退出 */
b end
is_nonzero:
/* Prepare the call to factorial(n-1) */
sub r0, r0, #1 /* r0 ← r0 - 1 */
bl factorial
/* After the call r0 contains factorial(n-1) */
/* Load r0 (that we kept in th stack) into r1 */
ldr r1, [sp] /* r1 ← *sp */
mul r0, r1 /* r0 ← r0 * r1 */
end:
add sp, sp, #+4 /* 丢弃r0参数*/
ldr lr, [sp], #+4 /* 加载源lr的寄存器内容重新到lr寄存器中 */
bx lr /* 退出factorial函数*/
.global main
main:
str lr, [sp,#-4]! /* 保存lr到堆栈中*/
sub sp, sp, #4 /* 留出一个4字节空间,给用户输入保存*/
ldr r0, address_of_message1 /* 打印mesg1*/
bl printf /* 调用 printf */
ldr r0, address_of_format /* scanf的格式化字符串参数 */
mov r1, sp /* 堆栈顶层作为scanf的第二个参数*/
bl scanf /* 调用scanf */
ldr r0, [sp] /* 加载输入的参数给r0 */
bl factorial /* 调用factorial */
mov r2, r0 /* 结果赋值给r2,作为printf第三个参数 */
ldr r1, [sp] /* 读入的整数,作为printf第二个参数*/
ldr r0, address_of_message2 /*作为printf第一个参数*/
bl printf /* 调用printf */
add sp, sp, #+4 /* 抛弃第一个scanf读入的值 */
ldr lr, [sp], #+4 /* 弹出保存的lr*/
bx lr /* 退出*/
address_of_message1: .word message1
address_of_message2: .word message2
address_of_format: .word format
as -g -o fact.o fact.s
gcc -o fact fact.o
$./fact
Type a number: 9
The factorial of 9 is 362880
通过一次LOAD/STORE多个数据,load multiple指令ldm 和store multiple,指令stm。
ldm addressing-mode Rbase{!}, register-set
stm addressing-mode Rbase{!}, register-set。
就是块拷贝寻址,可实现连续地址数据从存储器的某一位置拷贝到另一位置。
LDMIA/STMIA
LDMDA/STMDA
LDMIB/STMIB
LDMDB/STMDB
LDMIA R0!, {R1-R3}
从R0寄存器的存储单元中读取3个字到R1-R3寄存器中。
STMIA R0!, {R1-R3}
存储在R1-R3寄存器的内容到R0指向ed存储单元。
LDMIA/LDMDA中I表示Increasing,D表示decreasing,A表示After,B表示Before。
.data
message1: .asciz "Type a number: "
format: .asciz "%d"
message2: .asciz "The factorial of %d is %d\n"
.text
factorial:
stmdb sp!,{r4,lr}//将r4,lr保存到sp执行的栈中,因为db所以是先减,后加载。 有感叹号,所以最后是保持最小的值。符合栈的要求。
mov r4,r0
cmp r0, #0 /* compare r0 and 0 */
bne is_nonzero /* if r0 != 0 then branch */
mov r0, #1 /* r0 ← 1. This is the return */
b end
is_nonzero:
/* Prepare the call to factorial(n-1) */
sub r0, r0, #1 /* r0 ← r0 - 1 */
bl factorial
/* After the call r0 contains factorial(n-1) */
/* Load initial value of r0 (that we kept in r4) into r1 */
mov r1, r4 /* r1 ← r4 */
mul r0, r1 /* r0 ← r0 * r1 */
end:
ldmia sp!,{r4,lr}//加载sp栈中的值给r4和lr,先加载数值,后处理sp寄存器,最后sp是最后那个最大的值。符合栈的要求。
bx lr /* Leave factorial */
.global main
main:
str lr, [sp,#-4]! /* Push lr onto the top of the stack */
sub sp, sp, #4 /* Make room for one 4 byte integer in the stack */
/* In these 4 bytes we will keep the number */
/* entered by the user */
/* Note that after that the stack is 8-byte aligned */
ldr r0, address_of_message1 /* Set &message1 as the first parameter of printf */
bl printf /* Call printf */
ldr r0, address_of_format /* Set &format as the first parameter of scanf */
mov r1, sp /* Set the top of the stack as the second parameter */
/* of scanf */
bl scanf /* Call scanf */
ldr r0, [sp] /* Load the integer read by scanf into r0 */
/* So we set it as the first parameter of factorial */
bl factorial /* Call factorial */
mov r2, r0 /* Get the result of factorial and move it to r2 */
/* So we set it as the third parameter of printf */
ldr r1, [sp] /* Load the integer read by scanf into r1 */
/* So we set it as the second parameter of printf */
ldr r0, address_of_message2 /* Set &message2 as the first parameter of printf */
bl printf /* Call printf */
add sp, sp, #+4 /* Discard the integer read by scanf */
ldr lr, [sp], #+4 /* Pop the top of the stack and put it in lr */
bx lr /* Leave main */
address_of_message1: .word message1
address_of_message2: .word message2
address_of_format: .word format
as -g -o fact-o.o fact-o.s
gcc -o fact-o fact-o.o
$./fact-o
Type a number: 9
The factorial of 9 is 362880
.data
message1: .asciz "Type a number: "
format: .asciz "%d"
message2: .asciz "The factorial of %d is %d\n"
.text
.type factorial,@function
.globl factorial
factorial:
stp x29, x30, [sp, -32]!//保存x29和x30,即fp和lr.
str x0, [sp,#-16] /* Push 参数r0 到堆栈,这个是函数的参数*/
cmp x0, #0 /* 对比r0 and 0 */
bne is_nonzero /* if r0 != 0 then 继续分支*/
mov x0, #1 /* 如果参数是0,则r0 ← 1,函数退出 */
b end
is_nonzero:
/* Prepare the call to factorial(n-1) */
sub x0, x0, #1 /* r0 ← r0 - 1 */
bl factorial
/* After the call r0 contains factorial(n-1) */
/* Load r0 (that we kept in th stack) into r1 */
ldr x1, [sp,#-16] /* r1 ← *sp */
mul x0,x0,x1
end:
ldp x29, x30, [sp], 32
ret
.global _start
_start:
sub sp, sp, #16 /* 留出一个4字节空间,给用户输入保存*/
ldr x0, address_of_message1 /* 打印mesg1*/
bl printf /* 调用 printf */
ldr x0, address_of_format /* scanf的格式化字符串参数 */
mov x1, sp /* 堆栈顶层作为scanf的第二个参数*/
bl scanf /* 调用scanf */
ldr x0, [sp] /* 加载输入的参数给r0 */
bl factorial /* 调用factorial */
mov x2, x0 /* 结果赋值给r2,作为printf第三个参数 */
ldr x1, [sp] /* 读入的整数,作为printf第二个参数*/
ldr x0, address_of_message2 /*作为printf第一个参数*/
bl printf /* 调用printf */
add sp, sp, #+16 /* 抛弃第一个scanf读入的值 */
mov x8, 93
svc 0
address_of_message1: .dword message1
address_of_message2: .dword message2
address_of_format: .dword format
as -g -o fact64.o fact64.s
ld -g -o fact64 fact64.o -lc -I /lib64/ld-linux-aarch64.so.1
执行完毕。