diff --git a/sched/sched/sched_backtrace.c b/sched/sched/sched_backtrace.c index 40c993c0a05..b16e29bf7ee 100644 --- a/sched/sched/sched_backtrace.c +++ b/sched/sched/sched_backtrace.c @@ -28,15 +28,36 @@ #include #include +#include + +#include + #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