sched/backtrace: fix cross-CPU buffer access under addrenv

The remote CPU's IPI handler wrote into the caller's buffer directly,
which may not be reachable from the target CPU's address environment
under CONFIG_ARCH_ADDRENV.

Signed-off-by: liang.huang <liang.huang@houmo.ai>
This commit is contained in:
liang.huang 2026-07-19 17:45:39 +08:00 committed by Alan C. Assis
parent 22ff8dbf8f
commit fd0dd8c597

View file

@ -28,15 +28,36 @@
#include <nuttx/sched.h>
#include <nuttx/init.h>
#include <sys/param.h>
#include <string.h>
#include "sched.h"
#ifdef CONFIG_ARCH_HAVE_BACKTRACE
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if defined(CONFIG_SMP) && defined(CONFIG_ARCH_ADDRENV)
/* Depth of the scratch buffer used to relay a remote backtrace back to
* the caller, since the caller's own buffer may not be mapped in the
* address environment active on the target CPU. Requests larger than
* this are serviced over multiple round trips.
*/
#define BACKTRACE_SCRATCH_DEPTH 32
#endif
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_SMP
struct backtrace_arg_s
{
pid_t pid;
@ -105,6 +126,11 @@ int sched_backtrace(pid_t tid, FAR void **buffer, int size, int skip)
FAR struct tcb_s *tcb = this_task();
int ret = 0;
if (size <= 0 || buffer == NULL)
{
return 0;
}
if (tcb->pid == tid)
{
ret = up_backtrace(tcb, buffer, size, skip);
@ -121,25 +147,88 @@ int sched_backtrace(pid_t tid, FAR void **buffer, int size, int skip)
tcb->task_state == TSTATE_TASK_RUNNING)
{
struct backtrace_arg_s arg;
bool need_restore;
#ifdef CONFIG_ARCH_ADDRENV
FAR void *scratch[BACKTRACE_SCRATCH_DEPTH];
#endif
if ((tcb->flags & TCB_FLAG_CPU_LOCKED) != 0)
{
arg.pid = tcb->pid;
arg.need_restore = false;
need_restore = false;
}
else
{
arg.pid = tcb->pid;
arg.need_restore = true;
need_restore = true;
tcb->flags |= TCB_FLAG_CPU_LOCKED;
}
arg.pid = tcb->pid;
#ifdef CONFIG_ARCH_ADDRENV
arg.buffer = scratch;
while (ret < size)
{
arg.size = MIN(size - ret, BACKTRACE_SCRATCH_DEPTH);
arg.skip = skip + ret;
/* If this round's request covers all remaining
* frames (i.e. it was not capped by the scratch
* buffer), there is nothing left to loop for
* afterwards, so it is safe to have it release the
* pin as well and skip the data-less round below.
*/
arg.need_restore = need_restore &&
arg.size == size - ret;
if (nxsched_smp_call_single(tcb->cpu,
sched_backtrace_handler,
&arg) < 0)
{
break;
}
if (arg.need_restore)
{
need_restore = false;
}
memcpy(&buffer[ret], scratch,
arg.stacksize * sizeof(FAR void *));
ret += arg.stacksize;
if (arg.stacksize < arg.size)
{
/* Reached the bottom of the target's stack. */
break;
}
}
/* The loop above may have exited without a round
* releasing the pin (e.g. it broke out on reaching the
* bottom of the stack before a "guaranteed last round"
* occurred). Send one more, data-less round purely to
* release it.
*/
if (need_restore)
{
arg.size = 0;
arg.need_restore = true;
nxsched_smp_call_single(tcb->cpu, sched_backtrace_handler,
&arg);
}
#else
arg.need_restore = need_restore;
arg.buffer = buffer;
arg.size = size;
arg.skip = skip;
ret = nxsched_smp_call_single(tcb->cpu,
sched_backtrace_handler,
&arg) < 0 ? 0 : arg.stacksize;
#endif
}
else
#endif