armv7-m/armv8-m: honor SP modifications on signal return

On ARMv7-M/ARMv8-M, the hardware exception return determines the final
SP from the physical location of the HW exception frame, ignoring any
software modification to REG_R13 in the saved register context.  This
means signal handlers that adjust SP have their change silently
discarded.

Fix this by relocating the saved context in arm_sigdeliver before
calling arm_fullcontextrestore.  If the desired SP (regs[REG_R13])
differs from the implied SP, memmove the entire context frame so that
the hardware exception return produces the correct final SP.

The fix runs only in the signal return path, adding zero overhead to the
normal exception return hot path.

Signed-off-by: Andrew Au <cshung@gmail.com>
This commit is contained in:
Andrew Au 2026-06-15 18:22:31 +00:00 committed by Alan C. Assis
parent 9704481224
commit d79beefa54
2 changed files with 50 additions and 0 deletions

View file

@ -27,6 +27,7 @@
#include <nuttx/config.h>
#include <stdint.h>
#include <string.h>
#include <sched.h>
#include <assert.h>
@ -57,6 +58,9 @@ void arm_sigdeliver(void)
{
struct tcb_s *rtcb = this_task();
uint32_t *regs = rtcb->xcp.saved_regs;
uint32_t *new_regs;
uint32_t desired_sp;
uint32_t implied_sp;
#ifdef CONFIG_SMP
/* In the SMP case, we must terminate the critical section while the signal
@ -163,6 +167,27 @@ retry:
leave_critical_section(up_irq_save());
#endif
/* If the signal handler modified SP (REG_R13), relocate the saved
* context so that the hardware exception return produces the correct SP.
*
* On ARMv7-M, the exception return path sets PSP to the HW frame address
* and hardware computes final SP = PSP + frame_size. The implied SP is
* determined by the physical location of the context, not by REG_R13.
* To honor a modified SP, we memmove the entire context frame to the
* address where the end of the frame equals the desired SP.
*/
desired_sp = regs[REG_R13];
implied_sp = (uint32_t)regs + XCPTCONTEXT_SIZE;
if (desired_sp != implied_sp)
{
new_regs = (uint32_t *)(desired_sp - XCPTCONTEXT_SIZE);
memmove(new_regs, regs, XCPTCONTEXT_SIZE);
regs = new_regs;
rtcb->xcp.saved_regs = new_regs;
}
rtcb->xcp.regs = rtcb->xcp.saved_regs;
arm_fullcontextrestore();
UNUSED(regs);

View file

@ -27,6 +27,7 @@
#include <nuttx/config.h>
#include <stdint.h>
#include <string.h>
#include <sched.h>
#include <assert.h>
@ -57,6 +58,9 @@ void arm_sigdeliver(void)
{
struct tcb_s *rtcb = this_task();
uint32_t *regs = rtcb->xcp.saved_regs;
uint32_t *new_regs;
uint32_t desired_sp;
uint32_t implied_sp;
#ifdef CONFIG_SMP
/* In the SMP case, we must terminate the critical section while the signal
@ -163,6 +167,27 @@ retry:
leave_critical_section(up_irq_save());
#endif
/* If the signal handler modified SP (REG_R13), relocate the saved
* context so that the hardware exception return produces the correct SP.
*
* On ARMv8-M, the exception return path sets PSP to the HW frame address
* and hardware computes final SP = PSP + frame_size. The implied SP is
* determined by the physical location of the context, not by REG_R13.
* To honor a modified SP, we memmove the entire context frame to the
* address where the end of the frame equals the desired SP.
*/
desired_sp = regs[REG_R13];
implied_sp = (uint32_t)regs + XCPTCONTEXT_SIZE;
if (desired_sp != implied_sp)
{
new_regs = (uint32_t *)(desired_sp - XCPTCONTEXT_SIZE);
memmove(new_regs, regs, XCPTCONTEXT_SIZE);
regs = new_regs;
rtcb->xcp.saved_regs = new_regs;
}
rtcb->xcp.regs = rtcb->xcp.saved_regs;
arm_fullcontextrestore();
UNUSED(regs);