Add architecture-independent user-space frame unwinder that uses
arch-provided instruction callbacks to walk the call stack.

The unwinder tries recovery in order:
  1. Signal frame: if arch reports PC looks like a signal trampoline,
     walks rt_sigframe -> ucontext -> sigcontext using vmcoreinfo
     offsets to recover saved PC, SP, and RA.
  2. Prologue scan: scans backward from PC looking for stack frame
     setup (SP adjustment) and RA save instructions. Uses a buffered
     read strategy with page-boundary handling for efficiency.

Architecture-specific behavior is provided via arch.unwind_ops:
  - is_sp_move_ins(): detect stack pointer adjustment
  - is_ra_save_ins(): detect return address save to stack
  - is_end_of_backtrace(): detect end-of-trace markers (optional)
  - post_unwind_fixup(): arch-specific post-processing (optional)
  - likely_signal_pc(): hint whether PC is in a signal trampoline
  - signal_layout: describes signal frame structure offsets

This separation allows adding new architectures by implementing
only the instruction decode callbacks, without duplicating the
unwinding logic.

Signed-off-by: Pnina Feder <[email protected]>
---
 vmcore_tasks/unwind.c | 267 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 267 insertions(+)
 create mode 100644 vmcore_tasks/unwind.c

diff --git a/vmcore_tasks/unwind.c b/vmcore_tasks/unwind.c
new file mode 100644
index 00000000..99d23322
--- /dev/null
+++ b/vmcore_tasks/unwind.c
@@ -0,0 +1,267 @@
+#include <stdio.h>
+#include <string.h>
+#include "memory.h"
+#include "vmcore_tasks_defs.h"
+
+/* Return codes for unwind_one_frame */
+#define UNWIND_OK        0
+#define UNWIND_LEAF     -1   /* No prologue found — possible leaf function */
+#define UNWIND_FAIL     -2   /* Hard failure (bad memory, etc.) */
+
+static bool sigframe_candidate_ok(uint64_t old_pc, uint64_t old_sp, uint64_t 
old_ra,
+                                 uint64_t new_pc, uint64_t new_sp, uint64_t 
new_ra)
+{
+       if (!new_pc || !new_sp)
+               return false;
+       if (new_pc == old_pc && new_ra == old_ra)
+               return false;
+       /* Signal return should restore caller SP above current sigframe SP. */
+       if (new_sp <= old_sp)
+               return false;
+       /* Reject absurd jumps in SP (false positives). */
+       if ((new_sp - old_sp) > (1ULL << 20))
+               return false;
+       return true;
+}
+
+static int unwind_signal_frame(struct stackframe *frame)
+{
+       const struct signal_unwind_layout *layout = 
arch.unwind_ops.signal_layout;
+       uint64_t old_pc, old_sp, old_ra;
+       uint64_t uc_addr, mctx_addr, regs_addr;
+       uint64_t new_pc = 0, new_sp = 0, new_ra = 0;
+
+       if (!frame || !layout)
+               return -1;
+
+       /* Required vmcoreinfo keys to locate rt_sigframe -> ucontext -> 
sigcontext regs. */
+       if (!layout->rt_sigframe_uc_key ||
+           !layout->ucontext_mcontext_key ||
+           !layout->sigcontext_regs_key)
+               return -1;
+
+       if (!OFFSET_EXISTS(layout->rt_sigframe_uc_key) ||
+           !OFFSET_EXISTS(layout->ucontext_mcontext_key) ||
+           !OFFSET_EXISTS(layout->sigcontext_regs_key))
+               return -1;
+
+       /* Walk the layout chain from current SP to saved register block. */
+       uc_addr   = frame->sp + OFFSET(layout->rt_sigframe_uc_key);
+       mctx_addr = uc_addr   + OFFSET(layout->ucontext_mcontext_key);
+       regs_addr = mctx_addr + OFFSET(layout->sigcontext_regs_key);
+
+       /* Always recover RA and SP from arch provided indices */
+       if (readmem(regs_addr + ((uint64_t)layout->ra_reg_index * 
layout->reg_width),
+           &new_ra, layout->reg_width, "sigcontext ra", UVADDR) < 0)
+               return -1;
+       if (readmem(regs_addr + ((uint64_t)layout->sp_reg_index * 
layout->reg_width),
+           &new_sp, layout->reg_width, "sigcontext sp", UVADDR) < 0)
+               return -1;
+
+       old_pc = frame->pc;
+       old_sp = frame->sp;
+       old_ra = frame->ra;
+
+       /* PC can be either a dedicated field or a slot in the regs array. */
+       if (layout->sigcontext_pc_key && 
OFFSET_EXISTS(layout->sigcontext_pc_key)) {
+               uint64_t pc_addr = mctx_addr + 
OFFSET(layout->sigcontext_pc_key);
+               if (readmem(pc_addr, &new_pc, layout->reg_width,
+                   "sigcontext pc", UVADDR) < 0)
+                       return -1;
+       } else {
+               if (readmem(regs_addr + ((uint64_t)layout->pc_reg_index * 
layout->reg_width),
+                                &new_pc, layout->reg_width, "sigcontext pc", 
UVADDR) < 0)
+                       return -1;
+               /* Try alternate PC slot for kernels/ABIs with different 
ordering. */
+               if (!sigframe_candidate_ok(old_pc, old_sp, old_ra, new_pc, 
new_sp, new_ra) &&
+                   layout->pc_reg_alt_index >= 0) {
+                       if (readmem(regs_addr + 
((uint64_t)layout->pc_reg_alt_index * layout->reg_width),
+                                        &new_pc, layout->reg_width, 
"sigcontext pc(alt)", UVADDR) < 0)
+                               return -1;
+               }
+       }
+
+       /* Final sanity gate before committing recovered frame. */
+       if (!sigframe_candidate_ok(old_pc, old_sp, old_ra, new_pc, new_sp, 
new_ra))
+               return -1;
+
+       pr_verbose("sigframe-candidate: old[pc=0x%016llx sp=0x%016llx 
ra=0x%016llx] new[pc=0x%016llx sp=0x%016llx ra=0x%016llx] chain[uc=0x%016llx 
mctx=0x%016llx regs=0x%016llx] src=%s idx[sp=%d ra=%d pc=%d alt=%d] w=%u\n",
+                  (unsigned long long)old_pc, (unsigned long long)old_sp, 
(unsigned long long)old_ra,
+                  (unsigned long long)new_pc, (unsigned long long)new_sp, 
(unsigned long long)new_ra,
+                  (unsigned long long)uc_addr, (unsigned long long)mctx_addr, 
(unsigned long long)regs_addr,
+                  (layout->sigcontext_pc_key && 
OFFSET_EXISTS(layout->sigcontext_pc_key)) ? "sigcontext_pc_key" : "regs[]",
+                  layout->sp_reg_index, layout->ra_reg_index, 
layout->pc_reg_index, layout->pc_reg_alt_index,
+                  layout->reg_width);
+
+       frame->pc = (unsigned long)new_pc;
+       frame->sp = (unsigned long)new_sp;
+       frame->ra = (unsigned long)new_ra;
+       return 0;
+}
+
+/*
+ * Try to recover the previous frame by scanning backward for prologue
+ * instructions (SP adjustment + RA save).
+ *
+ * Returns: UNWIND_OK on success, UNWIND_LEAF if no prologue found,
+ *          UNWIND_FAIL on hard error.
+ */
+static int unwind_prologue_scan(struct stackframe *frame, unsigned int 
max_check)
+{
+       off_t ra_offset = 0;
+       size_t stack_size = 0;
+       unsigned long scan_addr, scan_end;
+       uint32_t insn;
+       int sp_adjust = 0;
+       unsigned long ra_slot = 0;
+
+       uint8_t code_buf[CODE_BUF_SIZE];
+       unsigned long buf_base = 0;
+       ssize_t buf_len = 0;
+
+       scan_addr = frame->pc - arch.unwind_ops.insn_size;
+
+       if (max_check >= frame->pc)
+               scan_end = 0;
+       else
+               scan_end = frame->pc - max_check;
+
+       while (scan_addr >= scan_end) {
+               /* Refill code buffer when needed */
+               if (buf_len == 0 || scan_addr < buf_base) {
+                       if (scan_addr + arch.unwind_ops.insn_size < 
CODE_BUF_SIZE)
+                               buf_base = 0;
+                       else
+                               buf_base = scan_addr + 
arch.unwind_ops.insn_size - CODE_BUF_SIZE;
+
+                       if (buf_base < scan_end)
+                               buf_base = scan_end & ~((unsigned 
long)arch.unwind_ops.insn_size - 1);
+
+                       size_t to_read = scan_addr + arch.unwind_ops.insn_size 
- buf_base;
+                       buf_len = readmem(buf_base, code_buf, to_read,
+                                         "read code chunk", UVADDR);
+                       if (buf_len <= 0) {
+                               unsigned long next_page = (buf_base & 
SYS_PAGE_MASK) + SYS_PAGE_SIZE;
+                               pr_trace("Failed to read at 0x%lx, advancing 
scan_end to 0x%lx\n",
+                                        buf_base, next_page);
+                               scan_end = next_page;
+                               buf_len = 0;
+                               continue;
+                       }
+               }
+
+               size_t offset = scan_addr - buf_base;
+               if (offset + arch.unwind_ops.insn_size > (size_t)buf_len) {
+                       pr_verbose("Offset out of buffer range\n");
+                       return UNWIND_FAIL;
+               }
+               memcpy(&insn, code_buf + offset, arch.unwind_ops.insn_size);
+
+               if (arch.unwind_ops.is_sp_move_ins(insn, &sp_adjust)) {
+                       if (sp_adjust < 0) {
+                               stack_size = (size_t)(-sp_adjust);
+                               pr_trace("sp_move: pc=0x%lx insn=0x%08x 
adjust=%d\n",
+                                        scan_addr, insn, sp_adjust);
+                               break;
+                       }
+               } else if (arch.unwind_ops.is_ra_save_ins(insn, &ra_slot)) {
+                       pr_trace("ra_save: pc=0x%lx insn=0x%08x slot=%lu\n",
+                                scan_addr, insn, ra_slot);
+                       ra_offset = (off_t)ra_slot;
+               }
+
+               if (scan_addr < arch.unwind_ops.insn_size)
+                       break;
+               scan_addr -= arch.unwind_ops.insn_size;
+       }
+
+       if (!stack_size)
+               return UNWIND_LEAF;
+
+       if (ra_offset) {
+               unsigned long ra_addr = frame->sp + ra_offset;
+               unsigned long new_ra;
+               if (readmem(ra_addr, &new_ra, arch.unwind_ops.ra_width,
+                           "read RA from stack", UVADDR) != 
(ssize_t)arch.unwind_ops.ra_width) {
+                       pr_trace("RA slot unreadable at 0x%lx; treating as end 
of backtrace\n",
+                                ra_addr);
+                       return UNWIND_FAIL;
+               }
+               frame->ra = new_ra;
+               frame->pc = new_ra;
+       } else if (frame->ra != 0) {
+               /* No RA slot found — use existing RA as fallback (e.g. from 
registers) */
+               frame->pc = frame->ra;
+       } else {
+               fprintf(stderr, "No RA found and frame->ra is 0\n");
+               return UNWIND_FAIL;
+       }
+
+       frame->sp += stack_size;
+
+       pr_trace("unwound: pc=0x%lx ra=0x%lx sp=0x%lx\n",
+                frame->pc, frame->ra, frame->sp);
+       return UNWIND_OK;
+}
+
+/*
+ * Try signal-frame recovery if the arch hints that PC looks like a
+ * signal trampoline.
+ */
+static int try_signal_frame_unwind(struct stackframe *frame)
+{
+       if (!arch.unwind_ops.signal_layout)
+               return -1;
+
+       bool likely = true;
+       if (arch.unwind_ops.likely_signal_pc)
+               likely = arch.unwind_ops.likely_signal_pc(frame->pc);
+
+       if (!likely)
+               return -1;
+
+       struct stackframe candidate = *frame;
+       if (unwind_signal_frame(&candidate) != 0)
+               return -1;
+
+       pr_verbose("signal-frame: pc 0x%lx->0x%lx sp 0x%lx->0x%lx ra 
0x%lx->0x%lx\n",
+                frame->pc, candidate.pc, frame->sp, candidate.sp,
+                frame->ra, candidate.ra);
+       *frame = candidate;
+       return 0;
+}
+
+/*
+ * Public entry point: unwind one user-space frame.
+ *
+ * Tries, in order:
+ *   1. Signal-frame recovery
+ *   2. Prologue-scan unwinding
+ *
+ * Returns UNWIND_OK, UNWIND_LEAF, or UNWIND_FAIL.
+ */
+int unwind_user_frame(struct stackframe *frame, unsigned int max_check)
+{
+       int ret;
+
+       if (!frame->pc || !frame->sp) {
+               fprintf(stderr, "error: pc or sp is zero: pc=0x%lx sp=0x%lx\n",
+                       frame->pc, frame->sp);
+               return UNWIND_FAIL;
+       }
+
+       if (try_signal_frame_unwind(frame) == 0)
+               return UNWIND_OK;
+
+       ret = unwind_prologue_scan(frame, max_check);
+       if (ret != UNWIND_OK)
+               return ret;
+
+       /* Optional arch-specific fixup after unwinding. */
+       if (arch.unwind_ops.post_unwind_fixup) {
+               if (arch.unwind_ops.post_unwind_fixup(frame, max_check) != 0)
+                       return UNWIND_FAIL;
+       }
+
+       return UNWIND_OK;
+}
\ No newline at end of file
-- 
2.43.0


Reply via email to