堆栈管理和寄存器分配无效

时间:2017-06-21 08:08:43

标签: gcc assembly optimization cortex-m thumb

请考虑以下代码:

extern unsigned int foo(char c, char **p, unsigned int *n);

unsigned int test(const char *s, char **p, unsigned int *n) 
{
        unsigned int done = 0;

        while (*s)
                done += foo(*s++, p, n); 

        return done;
}

汇编中的输出:

00000000 <test>:
   0:   b5f8        push    {r3, r4, r5, r6, r7, lr}
   2:   0005        movs    r5, r0
   4:   000e        movs    r6, r1
   6:   0017        movs    r7, r2
   8:   2400        movs    r4, #0
   a:   7828        ldrb    r0, [r5, #0]
   c:   2800        cmp r0, #0
   e:   d101        bne.n   14 <test+0x14>
  10:   0020        movs    r0, r4
  12:   bdf8        pop {r3, r4, r5, r6, r7, pc}
  14:   003a        movs    r2, r7
  16:   0031        movs    r1, r6
  18:   f7ff fffe   bl  0 <foo>
  1c:   3501        adds    r5, #1
  1e:   1824        adds    r4, r4, r0
  20:   e7f3        b.n a <test+0xa>

使用 arm-none-eabi-gcc 版本编译的C代码:4.9.1,5.4.0,6.3.0和7.1.0 on Linux主机。所有GCC版本的装配输出都相同。

CFLAGS:= -Os -march = armv6-m -mcpu = cortex-m0plus -mthumb

我对执行流程的理解如下:

  1. 将R3-R7 + LR推入堆栈(完全不清楚)
  2. 将R0移至R5(这很清楚)
  3. 将R1移至R6,将R2移至R7(完全不清楚)
  4. 将R5取消引入R0(这很清楚)
  5. 将R0与0比较(这是明确的)
  6. 如果R0!= 0,请转到第14行: - 从R6恢复R1,从R7恢复R2并调用foo(), 如果R0 == 0保持在第10行,则从堆栈恢复R3 - R7 + PC(完全不清楚)
  7. 增量R5(清除)
  8. 累积来自foo()的结果(清除)
  9. 分支回到a :(清除)
  10. 我自己的大会。没有经过广泛测试,但我绝对不需要将R4 + LR推到堆栈上:

    编辑:根据提供的答案,我的下面的示例将失败,因为R1和R2没有通过调用foo来持久()

    51 unsigned int __attribute__((naked)) test_asm(const char *s, char **p, unsigned int *n)
    52 {
    53         // r0 - *s (move ptr to r3 and dereference it to r0)
    54         // r1 - **p
    55         // r2 - *n
    56         asm volatile(
    57                 "   push {r4, lr}               \n\t"
    58                 "   movs r4, #0                 \n\t"
    59                 "   movs r3, r0                 \n\t"
    60                 "1:                             \n\t"
    61                 "   ldrb r0, [r3, #0]           \n\t"
    62                 "   cmp r0, #0                  \n\t"
    63                 "   beq 2f                      \n\t"
    64                 "   bl foo                      \n\t"
    65                 "   add r4, r4, r0              \n\t"
    66                 "   add r3, #1                  \n\t"
    67                 "   b 1b                        \n\t"
    68                 "2:                             \n\t"
    69                 "   movs r0, r4                 \n\t"
    70                 "   pop {r4, pc}                \n\t"
    71         );
    72 }
    

    问题:

    1. 为什么GCC会为这么重要的功能存储这么多寄存器?

    2. 为什么在用ABI写入R0-R3为参数寄存器时推送R3 并且应该是调用者保存,应该在被调用的函数内安全使用 在这种情况下test()

    3. 为什么它将R1复制到R6和R2复制到R7,而extern函数的原型差不多 理想情况下匹配test()函数。所以R1和R2已经准备好通过了 到foo()例程。我的理解是,之前只需要取消引用R0 致电foo()

1 个答案:

答案 0 :(得分:2)

  1. 由于test不是叶子函数,因此必须保存LR。函数使用r5-r7来存储跨函数调用使用的值,因为它们不是临时的,所以必须保存它们。推动r3以对齐堆栈。

  2. push添加额外的寄存器是一种快速而紧凑的方式来对齐堆栈。

  3. r1r2可能会被foo的调用破坏,因为在调用之后需要最初存储在这些寄存器中的值,它们必须存储在某个位置幸存的电话。