testing/sig_sp_test: add test for SP context restore on signal return

Add a test that verifies modifying SP (REG_R13) in the saved register
context is honored on signal return. The test pushes values onto the
stack, triggers SIGALRM, and in the handler advances SP to skip a
value, then verifies the correct value is popped after signal return.

This exercises the SP relocation fix in arm_sigdeliver.c for ARMv7-M
and ARMv8-M architectures.

Signed-off-by: Andrew Au <cshung@gmail.com>
This commit is contained in:
Andrew Au 2026-06-21 17:26:28 +00:00 committed by Alin Jerpelea
parent b8cb8d1a4a
commit 15ea6e41d6
5 changed files with 288 additions and 0 deletions

View file

@ -0,0 +1,20 @@
# ##############################################################################
# apps/testing/sig_sp_test/CMakeLists.txt
#
# SPDX-License-Identifier: Apache-2.0
#
# ##############################################################################
if(CONFIG_TESTING_SIG_SP_TEST)
nuttx_add_application(
NAME
${CONFIG_TESTING_SIG_SP_TEST_PROGNAME}
PRIORITY
${CONFIG_TESTING_SIG_SP_TEST_PRIORITY}
STACKSIZE
${CONFIG_TESTING_SIG_SP_TEST_STACKSIZE}
MODULE
${CONFIG_TESTING_SIG_SP_TEST}
SRCS
sig_sp_test_main.c)
endif()

View file

@ -0,0 +1,31 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
config TESTING_SIG_SP_TEST
tristate "Signal SP restore test"
default n
depends on BUILD_FLAT && ARCH_ARM
---help---
Test that modifying SP (REG_R13) in saved register context
during a signal handler is honored on exception return.
This verifies the SP context relocation in arm_sigdeliver.
Only available for flat builds on ARM.
if TESTING_SIG_SP_TEST
config TESTING_SIG_SP_TEST_PROGNAME
string "Program name"
default "sig_sp_test"
config TESTING_SIG_SP_TEST_PRIORITY
int "Task priority"
default 100
config TESTING_SIG_SP_TEST_STACKSIZE
int "Stack size"
default 4096
endif

View file

@ -0,0 +1,10 @@
############################################################################
# apps/testing/sig_sp_test/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
############################################################################
ifneq ($(CONFIG_TESTING_SIG_SP_TEST),)
CONFIGURED_APPS += $(APPDIR)/testing/sig_sp_test
endif

View file

@ -0,0 +1,17 @@
############################################################################
# apps/testing/sig_sp_test/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
############################################################################
include $(APPDIR)/Make.defs
PROGNAME = $(CONFIG_TESTING_SIG_SP_TEST_PROGNAME)
PRIORITY = $(CONFIG_TESTING_SIG_SP_TEST_PRIORITY)
STACKSIZE = $(CONFIG_TESTING_SIG_SP_TEST_STACKSIZE)
MODULE = $(CONFIG_TESTING_SIG_SP_TEST)
MAINSRC = sig_sp_test_main.c
include $(APPDIR)/Application.mk

View file

@ -0,0 +1,210 @@
/****************************************************************************
* apps/testing/sig_sp_test/sig_sp_test_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Test: Verify that modifying SP (REG_R13) in the saved register context
* is honored on context restore (exception return path).
*
* Scenario (simulates runtime unwinding past a trampoline):
* 1. Push values 1 and 2 onto the stack (simulating a trampoline push)
* 2. Busy-wait for a timer signal (SIGALRM)
* 3. In the handler, "emulate a pop" by advancing SP by 4
* (simulating a runtime deciding the top frame was a stub)
* 4. Redirect PC to resume_after_signal
* 5. After signal return, pop a value it should be 1
*
* This exercises:
* - The SP relocation fix in arm_exception.S
* - The backward sliding copy (new SP > old SP, stack shrinks)
* - A realistic use case: unwinding past a trampoline frame
*
* Requirements:
* - Flat build only (accesses nxsched_self() for saved_regs)
* - ARM architecture (uses ARMv7-M register layout)
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <nuttx/arch.h>
#include <nuttx/sched.h>
#include <arch/irq.h>
/****************************************************************************
* Private Data
****************************************************************************/
static volatile int g_result = -1;
static volatile int g_ready = 0;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: verify_result
*
* Description:
* Called with the popped value in r0. Verifies it equals 1.
****************************************************************************/
void __attribute__((noinline, used)) verify_result(uint32_t value)
{
printf("sig_sp_test: popped value = %lu (expected 1)\n",
(unsigned long)value);
if (value == 1)
{
printf("sig_sp_test: PASS\n");
g_result = 0;
}
else
{
printf("sig_sp_test: FAIL - expected 1, got %lu\n",
(unsigned long)value);
g_result = 1;
}
exit(g_result);
}
/****************************************************************************
* Name: resume_after_signal
*
* Description:
* We land here after the signal handler adjusts SP and PC.
* Stack now has [SP] = 1 (the '2' was skipped by SP += 4).
* Pop one value and verify it equals 1.
****************************************************************************/
static void __attribute__((naked, used)) resume_after_signal(void)
{
__asm__ __volatile__(
"pop {r0}\n\t"
"b verify_result\n\t"
);
}
/****************************************************************************
* Name: sigalrm_handler
*
* Description:
* Called from the timer interrupt path (async signal delivery).
* Accesses saved_regs via nxsched_self() to modify the context
* that will be restored after signal return.
****************************************************************************/
static void sigalrm_handler(int signo, siginfo_t *info, void *ucontext)
{
struct tcb_s *rtcb = nxsched_self();
uint32_t *regs;
(void)signo;
(void)info;
(void)ucontext;
regs = rtcb->xcp.saved_regs;
if (regs == NULL)
{
printf("sig_sp_test: ERROR - saved_regs is NULL\n");
exit(2);
}
printf("sig_sp_test: handler - PC=0x%08lx SP=0x%08lx\n",
(unsigned long)regs[REG_R15],
(unsigned long)regs[REG_R13]);
/* Only act when the main code is ready (in the asm loop) */
if (!g_ready)
{
alarm(1);
return;
}
/* Emulate a pop: advance SP by 4 (skip top-of-stack value '2') */
regs[REG_R13] += 4;
/* Redirect execution to resume_after_signal */
regs[REG_R15] = ((uint32_t)(uintptr_t)resume_after_signal) & ~1u;
regs[REG_XPSR] |= (1 << 24);
printf("sig_sp_test: handler - new SP=0x%08lx PC=0x%08lx\n",
(unsigned long)regs[REG_R13],
(unsigned long)regs[REG_R15]);
}
/****************************************************************************
* Name: wait_with_values_on_stack
*
* Description:
* Push 1 and 2 on the stack, set g_ready, then loop forever.
* The alarm handler will redirect us out of the loop.
* Stack after pushes: [SP] = 2, [SP+4] = 1
****************************************************************************/
static void __attribute__((naked, used)) wait_with_values_on_stack(void)
{
__asm__ __volatile__(
"mov r0, #1\n\t"
"push {r0}\n\t"
"mov r0, #2\n\t"
"push {r0}\n\t"
"ldr r0, =g_ready\n\t"
"mov r1, #1\n\t"
"str r1, [r0]\n\t"
"1: nop\n\t"
"b 1b\n\t"
);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* sig_sp_test_main
****************************************************************************/
int main(int argc, char *argv[])
{
struct sigaction sa;
(void)argc;
(void)argv;
printf("sig_sp_test: Signal SP restore test\n");
printf("sig_sp_test: push 1, push 2, alarm, handler SP+=4, pop => 1\n");
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigalrm_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) < 0)
{
printf("sig_sp_test: ERROR sigaction failed\n");
return 1;
}
alarm(1);
wait_with_values_on_stack();
/* Should never reach here */
printf("sig_sp_test: ERROR - returned from wait\n");
return 1;
}