This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 2a6059a206e arch/sim: walk frame-pointer chain for non-running tasks 
in up_backtrace
2a6059a206e is described below

commit 2a6059a206ed89d8d028dba513353f491c6a04cc
Author: Lingao Meng <[email protected]>
AuthorDate: Wed Jun 3 19:35:01 2026 +0800

    arch/sim: walk frame-pointer chain for non-running tasks in up_backtrace
    
    The previous up_backtrace() relied entirely on host_backtrace() (a thin
    wrapper around glibc's backtrace()), which can only unwind the host
    thread that calls it.  As a result, when assert / dump_tasks() walked the
    task list and called sched_dumpstack() for every task, every task other
    than the currently-running one returned a zero-length backtrace, and the
    output was silently dropped.  In practice this meant that on sim only
    the crashing task ever produced a usable trace.
    
    Fix this by walking the frame-pointer chain ourselves whenever the
    target tcb is not the running task.  Because sim's setjmp/longjmp is
    provided by NuttX itself (libs/libc/machine/sim/arch_setjmp_*.S) and not
    by host libc, the rbp/rsp/rip (or arm fp/sp/pc) saved in tcb->xcp.regs
    are plain unmangled pointers, identical across Linux, macOS and Windows
    hosts.  The frame layout ([fp]=prev fp, [fp+1]=return address) is also
    shared by every host ABI sim supports (x86, x86_64, ARM, ARM64).
    
    The walker validates that fp lies inside the task's stack and is
    properly aligned, and stops when fp[0] is NULL, so a corrupted stack
    cannot make us read out of bounds.  The running-task path is unchanged
    and still uses host_backtrace() so DWARF unwinding through host
    libraries continues to work.
    
    Requires CONFIG_FRAME_POINTER=y so the compiler emits a usable fp link.
    
    Signed-off-by: Lingao Meng <[email protected]>
---
 arch/sim/src/sim/sim_backtrace.c | 128 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 118 insertions(+), 10 deletions(-)

diff --git a/arch/sim/src/sim/sim_backtrace.c b/arch/sim/src/sim/sim_backtrace.c
index b1b72f2d789..659b5b42357 100644
--- a/arch/sim/src/sim/sim_backtrace.c
+++ b/arch/sim/src/sim/sim_backtrace.c
@@ -25,38 +25,146 @@
  ****************************************************************************/
 
 #include <nuttx/arch.h>
+#include <nuttx/irq.h>
 #include <sched/sched.h>
 
+#include <setjmp.h>
+#include <stdint.h>
 #include <string.h>
 
 #include "sim_internal.h"
 
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: backtrace_fp
+ *
+ * Description:
+ *   Walk the frame-pointer chain to recover return addresses for a task
+ *   that is not currently running on the host thread.  This relies on the
+ *   uniform frame layout used by every host ABI sim supports (x86, x86_64,
+ *   ARM, ARM64):
+ *     [fp + 0] = previous fp
+ *     [fp + 1] = return address
+ *   where one slot is sizeof(uintptr_t).
+ *
+ *   Because sim's setjmp implementation is provided by NuttX itself
+ *   (libs/libc/machine/sim/arch_setjmp_*.S) rather than by host libc, the
+ *   saved fp/sp/pc in tcb->xcp.regs are plain pointers and contain no
+ *   pointer-mangling, so the chain can be followed directly on Linux,
+ *   macOS and Windows hosts.
+ *
+ ****************************************************************************/
+
+nosanitize_address
+static int backtrace_fp(uintptr_t *base, uintptr_t *limit,
+                        uintptr_t *fp, uintptr_t *pc,
+                        void **buffer, int size, int *skip)
+{
+  int i = 0;
+
+  if (pc != NULL)
+    {
+      if ((*skip)-- <= 0)
+        {
+          buffer[i++] = pc;
+        }
+    }
+
+  while (i < size)
+    {
+      /* Validate fp lies within [base, limit) and is properly aligned. */
+
+      if (fp < base || fp >= limit ||
+          ((uintptr_t)fp & (sizeof(uintptr_t) - 1)) != 0)
+        {
+          break;
+        }
+
+      if (fp[0] == 0)
+        {
+          break;
+        }
+
+      if ((*skip)-- <= 0)
+        {
+          buffer[i++] = (void *)fp[1];
+        }
+
+      fp = (uintptr_t *)fp[0];
+    }
+
+  return i;
+}
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
 
+/****************************************************************************
+ * Name: up_backtrace
+ *
+ * Description:
+ *   up_backtrace() returns a backtrace for the TCB, in the array pointed to
+ *   by buffer.  A backtrace is the series of currently active function
+ *   calls for the program.  Each item in the array pointed to by buffer is
+ *   of type void *, and is the return address from the corresponding stack
+ *   frame.
+ *
+ *   For the running task we keep using host_backtrace() (which wraps
+ *   glibc's backtrace()) so that we benefit from DWARF unwinding through
+ *   host libraries.  For any other task host_backtrace() cannot help -- it
+ *   only knows how to walk the host thread that is calling it -- so we
+ *   instead walk the frame-pointer chain starting from the registers saved
+ *   in tcb->xcp.regs by setjmp() at the last context switch.  This requires
+ *   CONFIG_FRAME_POINTER=y so that the compiler emits a usable fp link.
+ *
+ ****************************************************************************/
+
 nosanitize_address
 int up_backtrace(struct tcb_s *tcb, void **buffer, int size, int skip)
 {
-  void *buf[skip + size];
-  int ret = 0;
-  int i;
+  struct tcb_s *rtcb = running_task();
+  irqstate_t flags;
+  int ret;
 
-  if (tcb == running_task())
+  if (size <= 0 || buffer == NULL)
     {
-      ret = host_backtrace(buf, skip + size);
+      return 0;
     }
 
-  if (ret <= skip)
+  if (tcb == NULL)
     {
-      return ret < 0 ? ret : 0;
+      tcb = rtcb;
     }
 
-  ret -= skip;
-  for (i = 0; i < ret; i++)
+  if (tcb == rtcb)
     {
-      buffer[i] = buf[skip + i];
+      void *buf[skip + size];
+
+      ret = host_backtrace(buf, skip + size);
+      if (ret <= skip)
+        {
+          return ret < 0 ? ret : 0;
+        }
+
+      ret -= skip;
+      memcpy(buffer, &buf[skip], ret * sizeof(void *));
+      return ret;
     }
 
+  flags = enter_critical_section();
+
+  ret = backtrace_fp((uintptr_t *)tcb->stack_base_ptr,
+                     (uintptr_t *)((uintptr_t)tcb->stack_base_ptr +
+                                   tcb->adj_stack_size),
+                     (uintptr_t *)tcb->xcp.regs[JB_FP],
+                     (uintptr_t *)tcb->xcp.regs[JB_PC],
+                     buffer, size, &skip);
+
+  leave_critical_section(flags);
+
   return ret;
 }

Reply via email to