了解有关AAPCS的gcc行为(在STM32上)

时间:2019-06-26 07:09:41

标签: gcc arm stm32

编辑:我完全知道函数asmCopy可能不是功能性的,我的问题更多是关于gcc在寄存器中传递参数的行为。

我正在使用构建器为arm-none-eabi-gcc的STM32CubeIDE来开发STM32H7

优化级别为-Os

我看到以下无法解释的行为。我进行了屏幕截图,以获取并行的asm和C代码。

我的C代码正在调用3个函数。第一个和第三个具有完全相同的参数。

第二个不带参数。这是它的代码:

static void Reset_Cycle_Counter(void)
{
    volatile unsigned long *DWT_CYCCNT = (unsigned long *)0xE0001004;
    volatile unsigned long *DWT_CONTROL = (uint32_t *)0xE0001000;

    // Reset cycle counter
    *DWT_CONTROL = *DWT_CONTROL & ~0x00000001 ;
    *DWT_CYCCNT = 0;
    *DWT_CONTROL = *DWT_CONTROL | 1 ;
}

第三个功能很特别:我正在尝试编写一些汇编代码(现在很可能是错误的)。

static void __attribute__((noinline)) asmCopy(void *dst, void *src, uint32_t bytes)
{
    while (bytes--)
    {
        asm("ldrb r12,[r1], #1"); // src param is stored in r1, r12 can be modified without being restored after
        asm("strb r12,[r0], #1"); // dst paramis stored in r0
    }
}

在第一次(对memcpy的)函数调用之前,向r0,r1和r2加载了正确的值。

enter image description here

然后在调用第三个函数之前,正如您在下面看到的,r1和r2中的参数是错误的(qspi_addr应该为0x90000000)。 enter image description here

我对AAPCS(ARM上的过程调用标准)的理解是,在调用子例程之前,应将函数的参数(如果有的话)装入寄存器r0至r3。并且子例程不需要保留或还原这些寄存器。然后,第二个函数修改r1和r2是正常的。因此,我希望编译器在第三个调用之前更新r0,r1和r2。

如果我将优化代码更改为-O0,则确实可以达到预期的效果。

您怎么看?

3 个答案:

答案 0 :(得分:3)

您不能只打开一个内联汇编块并假定r0和r1仍然包含函数参数。对此没有任何保证。如果需要使用参数,则需要正确地将其作为输入和/或输出操作数传递

static void __attribute__((noinline))
myAsmCopy(void* dst, void* src, uint32_t bytes) {
  asm volatile("1: cbz %[bytes], 1f \n"
               "ldrb r12, [%[src]], #1 \n"
               "strb r12, [%[dst]], #1 \n"
               "subs %[bytes], #1 \n"
               "b 1b \n"
               "1: \n"
               : [dst] "+&r"(dst), [src] "+&r"(src), [bytes] "+&r"(bytes)
               :
               : "cc", "memory", "r12");
}

GCC在此处提供了有关内联汇编的大量文档: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

由于您显然从未使用过任何一种方法,因此我必须对此提出强烈建议。如果“ C包含脚枪”,那么内联汇编会将6发左轮手枪和5发子弹放在您的头上。

答案 1 :(得分:0)

如果您尝试询问编译器如何对其进行存档,那么一切都会变得越来越容易

https://godbolt.org/z/rXxeRe

void __attribute__((noinline)) asmCopy(void *dst, void *src, uint32_t bytes)
{
    while (bytes--)
    {
        asm("ldrb r12,[r1], #1"); // src param is stored in r1, r12 can be modified without being restored after
        asm("strb r12,[r0], #1"); // dst paramis stored in r0
    }
}

void __attribute__((noinline)) asmCopy1(void *dst, void *src, uint32_t bytes)
{
    while (bytes--)
    {
        *(uint8_t *)dst++ = *(uint8_t *)src++;
    }
}

和代码

asmCopy:
.L2:
        adds    r2, r2, #-1
        bcs     .L3
        bx      lr
.L3:
        ldrb r12,[r1], #1
        strb r12,[r0], #1
        b       .L2
asmCopy1:
        subs    r0, r0, #1
        add     r2, r2, r1
.L5:
        cmp     r1, r2
        bne     .L6
        bx      lr
.L6:
        ldrb    r3, [r1], #1    @ zero_extendqisi2
        strb    r3, [r0, #1]!
        b       .L5

答案 2 :(得分:0)

我想我找到了答案。

在我正在测试的函数中(无论是我实现的the脚,还是@Vinci的更好),传递给函数的某些参数是全局变量(用于执行某些测试的虚拟数据数组)。

我的理解是,编译器“修改”函数的原型以构建仅使用一个参数的函数。其他参数被视为常量,只是PC在函数开始时就相对加载了。

因此,我修改了代码以调用完全相同的函数,但使用了本地易失性指针,问题消失了:我可以看到寄存器r0,r1和r2加载了预期的参数。

这有意义吗?