【问题标题】:How to reset the stack pointer in "noreturn" functions on Cortex-M?如何在 Cortex-M 上的“noreturn”函数中重置堆栈指针?
【发布时间】:2019-08-20 06:27:13
【问题描述】:

为了减少堆栈溢出的可能性,我想在输入一个永远不会返回的函数后重置堆栈指针。我的代码中有两种情况会发生这种情况,main() 和一个将数据保存到闪存并进入深度睡眠的 shutdown_immediate() ISR。我使用 LTO 使代码适合,因此 main() 最终成为一个相当大的函数,需要为局部变量分配堆栈的一部分。

我的第一次尝试是使用__attribute__ ((noreturn)) 结合对__builtin_unreachable() 的调用,但这不会以任何方式更改生成的程序集。然后我创建了一个内联汇编函数,将堆栈指针重置为最后一个 SRAM 地址,该地址首先在 main() 和 shutdown_immediate() 处理程序中调用。

inline __attribute__((always_inline)) void NO_RETURN (void)
{
        extern const uint32_t __stack_top__; // Defined in .ld file
        asm volatile ("ldr r3, %[stack_top]\n"
                      "mov sp, r3\n"
                      : /* no outputs */
                      : [stack_top] "m" (__stack_top__)
                      : /* no clobbers */
        );
}

int main (void)
{
    NO_RETURN();

    /* rest of the code here... */
}

void shutdown_immediate (void)
{
    NO_RETURN();
}

这会为 ISR 生成看似正确的代码。对于main(),但是mov sp, r3 发生堆栈被分配给局部变量等之后。一旦主开始分支,这将失败。

生成的汇编代码:

00007f60 <shutdown_immediate>:
    7f60:   b570        push    {r4, r5, r6, lr}
    7f62:   4b21        ldr r3, [pc, #132]  ; (7fe8 <shutdown_immediate+0x88>)
    7f64:   681b        ldr r3, [r3, #0]
    7f66:   469d        mov sp, r3
; ...
    7fe8:   00202000    eoreq   r2, r0, r0 ; last SRAM address


00001180 <main>:
    1180:   b5f0        push    {r4, r5, r6, r7, lr}
    1182:   4be7        ldr r3, [pc, #924]  ; (1520 <main+0x3a0>)
    1184:   b097        sub sp, #92 ; 0x5c ; This SUB must be _after_ 0x1188!
    1186:   681b        ldr r3, [r3, #0]
    1188:   469d        mov sp, r3
; ...
    1520:   00202000    eoreq   r2, r0, r0 ; Last SRAM address

有没有人知道如何正确地做到这一点?我总是可以创建NO_RETURN() 函数的第二个变体,它将堆栈分配值作为参数,编译、反汇编、再次编译并在mov sp, r3 之后插入所需的sub sp, #nn,但这是一个混乱的解决方案。

架构:Cortex-M0

测试的工具链:

  • gcc-arm-none-eabi-6-2017-q2-update
  • gcc-arm-none-eabi-8-2018-q4-major

【问题讨论】:

  • 在进入后立即从堆栈指针中减去是编译器生成的函数序言的一部分。如果您需要在此之前重置堆栈指针,您可以使用“naked”属性 (gcc.gnu.org/onlinedocs/gcc/ARM-Function-Attributes.html) 来阻止 GCC 生成堆栈指针,或者,可能更明智和更安全的方法是在进入 main 之前重置堆栈指针。
  • 是的,naked for main 绝对是不可能的,因为根据 gcc 的文档,只有 asm 代码在此类函数中被认为是安全的。我想回收在 0x1180 处的初始指令中压入堆栈的 20 个字节。由于main() 是从复位处理程序中调用的并且永远不会返回,因此将这些寄存器推入堆栈是没有意义的。重置处理程序已经初始化了堆栈。我最初的希望是添加noreturn 属性会阻止编译器将推送注入序言,但没有这样的运气。
  • 可能没有办法“正确”地干预 C 代码中的堆栈指针。您是否尝试过使用 main() 作为重置的 ISR?

标签: gcc arm embedded cortex-m


【解决方案1】:

最强大的解决方案是在汇编中实现main,并在堆栈重置后从那里跳转到用户的main(根据需要重命名)。从长远来看,破解编译器逻辑太脆弱了。

【讨论】:

  • 已经是这样了; main 从程序集中实现的重置处理程序中调用。我想回收 main 函数序言中初始 push 消耗的堆栈,所以这没有帮助。
猜你喜欢
  • 1970-01-01
  • 2016-01-28
  • 1970-01-01
  • 2020-01-03
  • 1970-01-01
  • 2015-11-16
  • 1970-01-01
  • 2017-06-06
  • 1970-01-01
相关资源
最近更新 更多