In an attempt to mitigate the possibility of stack overflow I would like to reset the stack pointer after entering a function that will never return. There are two cases in my code where this occurs, main() and a shutdown_immediate() ISR that saves data to flash and enters deep sleep. I use LTO to make the code fit, so main() ends up being quite a large function that requires allocating part of the stack for local variables.
My first attempt was to use __attribute__ ((noreturn))
combined with a call to __builtin_unreachable()
, but that does not change the generated assembly in any way. I then created an inline assembly function to reset the stack pointer to the last SRAM address which is called first in main() and the shutdown_immediate() handler.
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();
}
This generates seemingly correct code for the ISR. For main()
however the mov sp, r3
happens after stack is allocated for local variables etc.. This will fail once main starts branching.
Generated assembly code:
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
Does anyone have any tricks for how this can be done correctly? I could always create a second variant of the NO_RETURN()
function which takes a stack allocation value as an argument, compile, disassemble, compile again and insert the required sub sp, #nn
after the mov sp, r3
, but that is a messy solution.
Architecture: Cortex-M0
Toolchains tested:
- gcc-arm-none-eabi-6-2017-q2-update
- gcc-arm-none-eabi-8-2018-q4-major
main()
is called from the reset handler and never returns, there is no point in pushing these registers to the stack. The reset handler already initialises the stack. My initial hope was that adding thenoreturn
attribute would prevent the compiler from injecting the push into the prologue, but no such luck. – tarjeik