nuttx/arch/risc-v/include/syscall.h
liang.huang cbe4ceb158 arch/risc-v: preserve ra across ecall for fp-chain backtrace
sys_callN() wraps a bare ecall and calls no other function, so the
compiler treats it as a leaf function: with frame pointers enabled,
it only needs to spill the caller's s0, which it places in what
up_backtrace()'s fp-chain walk assumes is ra's stack slot, while the
real ra slot is never written. sched_backtrace() then misreads that
slot as the return address for this frame, either resolving to a
bogus symbol or, if the adjacent garbage happens to look
out-of-range, terminating the backtrace early.

Add "ra" to the ecall clobber list so the compiler spills/reloads ra
around the ecall like a normal call site, keeping ra and the saved
s0 in their expected slots. Gate this on
CONFIG_FRAME_POINTER && CONFIG_SCHED_BACKTRACE, the only combination
where up_backtrace()'s fp-chain walk is both valid (FRAME_POINTER)
and actually exercised (SCHED_BACKTRACE); other configurations keep
the original "memory"-only clobber and pay no extra cost.

This only fixes the syscall boundary. Leaf functions that do not
cross a syscall (e.g. up_idle()) can still lose their ra slot the
same way and are not addressed here.

Signed-off-by: liang.huang <liang.huang@houmo.ai>
2026-07-16 15:24:12 +08:00

364 lines
11 KiB
C

/****************************************************************************
* arch/risc-v/include/syscall.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/* This file should never be included directly but, rather, only indirectly
* through include/syscall.h or include/sys/sycall.h
*/
#ifndef __ARCH_RISCV_INCLUDE_SYSCALL_H
#define __ARCH_RISCV_INCLUDE_SYSCALL_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#ifndef __ASSEMBLY__
# include <stdint.h>
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define SYS_syscall 0x00
/* Configuration ************************************************************/
/* This logic uses three system calls {0,1,2} for context switching and one
* for the syscall return. So a minimum of four syscall values must be
* reserved. If CONFIG_BUILD_FLAT isn't defined, then four more syscall
* values must be reserved.
*/
#ifndef CONFIG_BUILD_FLAT
# define CONFIG_SYS_RESERVED 6
#else
# define CONFIG_SYS_RESERVED 4
#endif
/* RISC-V system calls ******************************************************/
/* SYS call 1 and 2 are defined for internal use by the RISC-V port (see
* arch/risc-v/include/syscall.h). In addition, SYS call 3 is the
* return from a SYS call in kernel mode. The first four syscall values must,
* therefore, be reserved (0 is not used).
*/
/* SYS call 1:
*
* void riscv_fullcontextrestore() noreturn_function;
*/
#define SYS_restore_context (1)
/* SYS call 2:
*
* void riscv_switchcontext();
*/
#define SYS_switch_context (2)
#ifndef CONFIG_BUILD_FLAT
/* SYS call 4:
*
* void signal_handler(_sa_sigaction_t sighand, int signo,
* siginfo_t *info, void *ucontext);
*/
#define SYS_signal_handler (4)
/* SYS call 5:
*
* void signal_handler_return(void);
*/
#define SYS_signal_handler_return (5)
#endif /* !CONFIG_BUILD_FLAT */
/****************************************************************************
* Public Types
****************************************************************************/
/****************************************************************************
* Inline functions
****************************************************************************/
#ifndef __ASSEMBLY__
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
long smh_call(unsigned int nbr, void *parm);
#if defined(CONFIG_ARCH_USE_S_MODE) && defined(__KERNEL__)
uintptr_t sys_call0(unsigned int nbr);
uintptr_t sys_call1(unsigned int nbr, uintptr_t parm1);
uintptr_t sys_call2(unsigned int nbr, uintptr_t parm1, uintptr_t parm2);
uintptr_t sys_call3(unsigned int nbr, uintptr_t parm1, uintptr_t parm2,
uintptr_t parm3);
uintptr_t sys_call4(unsigned int nbr, uintptr_t parm1, uintptr_t parm2,
uintptr_t parm3, uintptr_t parm4);
uintptr_t sys_call5(unsigned int nbr, uintptr_t parm1, uintptr_t parm2,
uintptr_t parm3, uintptr_t parm4, uintptr_t parm5);
uintptr_t sys_call6(unsigned int nbr, uintptr_t parm1, uintptr_t parm2,
uintptr_t parm3, uintptr_t parm4, uintptr_t parm5,
uintptr_t parm6);
#else
/* ecall is a leaf "call" as far as the compiler can tell: sys_callN()
* itself never calls another function, so with frame pointers enabled
* the compiler stores the caller's saved fp (s0) at what up_backtrace()
* assumes is ra's slot, leaving ra's own slot never written. Clobbering
* "ra" forces it to be spilled/reloaded around the ecall so the fp-chain
* backtrace sched_backtrace() relies on can find it. Only worth the
* extra spill when both frame pointers and backtrace support are built
* in; otherwise there is no fp-chain consumer to fix up for.
*/
#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_SCHED_BACKTRACE)
# define RISCV_ECALL_CLOBBERS "memory", "ra"
#else
# define RISCV_ECALL_CLOBBERS "memory"
#endif
/****************************************************************************
* Name: sys_call0
*
* Description:
* System call SYS_ argument and no additional parameters.
*
****************************************************************************/
static inline uintptr_t sys_call0(unsigned int nbr)
{
register long r0 asm("a0") = (long)(nbr);
asm volatile
(
"ecall"
:: "r"(r0)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
/****************************************************************************
* Name: sys_call1
*
* Description:
* System call SYS_ argument and one additional parameter.
*
****************************************************************************/
static inline uintptr_t sys_call1(unsigned int nbr, uintptr_t parm1)
{
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
/****************************************************************************
* Name: sys_call2
*
* Description:
* System call SYS_ argument and two additional parameters.
*
****************************************************************************/
static inline uintptr_t sys_call2(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2)
{
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
register long r2 asm("a2") = (long)(parm2);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
/****************************************************************************
* Name: sys_call3
*
* Description:
* System call SYS_ argument and three additional parameters.
*
****************************************************************************/
static inline uintptr_t sys_call3(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2, uintptr_t parm3)
{
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
register long r2 asm("a2") = (long)(parm2);
register long r3 asm("a3") = (long)(parm3);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
/****************************************************************************
* Name: sys_call4
*
* Description:
* System call SYS_ argument and four additional parameters.
*
****************************************************************************/
static inline uintptr_t sys_call4(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2, uintptr_t parm3,
uintptr_t parm4)
{
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
register long r2 asm("a2") = (long)(parm2);
register long r3 asm("a3") = (long)(parm3);
register long r4 asm("a4") = (long)(parm4);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
/****************************************************************************
* Name: sys_call5
*
* Description:
* System call SYS_ argument and five additional parameters.
*
****************************************************************************/
static inline uintptr_t sys_call5(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2, uintptr_t parm3,
uintptr_t parm4, uintptr_t parm5)
{
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
register long r2 asm("a2") = (long)(parm2);
register long r3 asm("a3") = (long)(parm3);
register long r4 asm("a4") = (long)(parm4);
register long r5 asm("a5") = (long)(parm5);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
/****************************************************************************
* Name: sys_call6
*
* Description:
* System call SYS_ argument and six additional parameters.
*
****************************************************************************/
static inline uintptr_t sys_call6(unsigned int nbr, uintptr_t parm1,
uintptr_t parm2, uintptr_t parm3,
uintptr_t parm4, uintptr_t parm5,
uintptr_t parm6)
{
register long r0 asm("a0") = (long)(nbr);
register long r1 asm("a1") = (long)(parm1);
register long r2 asm("a2") = (long)(parm2);
register long r3 asm("a3") = (long)(parm3);
register long r4 asm("a4") = (long)(parm4);
register long r5 asm("a5") = (long)(parm5);
register long r6 asm("a6") = (long)(parm6);
asm volatile
(
"ecall"
:: "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5), "r"(r6)
: RISCV_ECALL_CLOBBERS
);
asm volatile("nop" : "=r"(r0));
return r0;
}
#endif
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __ASSEMBLY__ */
#endif /* __ARCH_RISCV_INCLUDE_SYSCALL_H */