天天看點

第19部分- Linux ARM彙編 函數調用棧使用-階乘

第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