第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

Type a number: 9

The factorial of 9 is 362880

 

阶乘示例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

as -g -o fact64.o fact64.s

ld -g -o fact64 fact64.o  -lc -I /lib64/ld-linux-aarch64.so.1

执行完毕。

你可能感兴趣的:(64位,ARM处理器汇编技术系列)