第19部分- Linux ARM彙編 函數調用棧使用-階乘
調用棧我們以階乘為例。階乘比較經典。
堆棧定義:堆棧是僅由目前動态激活擁有的記憶體區域。
我們先來看下階乘的C代碼如下:
int factorial(int n)
{
if (n == 0)
return 1;
else
return n * factorial(n-1);
}
階乘示例32位
.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
階乘示例64位
.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