On Wed, 16 Jul 2025 20:49:10 -0400 Steven Rostedt <rostedt@xxxxxxxxxx> wrote: > The code for this series is located here: > > git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git > unwind/core > > Head SHA1: f14e91fa8019acefb146869eb465966a88ef6f3b > > Changes since v13: https://lore.kernel.org/linux-trace-kernel/20250708012239.268642741@xxxxxxxxxx/ Here's a diff between v13 and v14: diff --git a/arch/Kconfig b/arch/Kconfig index 2c41d3072910..8e3fd723bd74 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -442,10 +442,6 @@ config HAVE_UNWIND_USER_FP bool select UNWIND_USER -config HAVE_UNWIND_USER_COMPAT_FP - bool - depends on HAVE_UNWIND_USER_FP - config HAVE_PERF_REGS bool help diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 17d4094c821b..5862433c81e1 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -302,7 +302,6 @@ config X86 select HAVE_SYSCALL_TRACEPOINTS select HAVE_UACCESS_VALIDATION if HAVE_OBJTOOL select HAVE_UNSTABLE_SCHED_CLOCK - select HAVE_UNWIND_USER_COMPAT_FP if IA32_EMULATION select HAVE_UNWIND_USER_FP if X86_64 select HAVE_USER_RETURN_NOTIFIER select HAVE_GENERIC_VDSO diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h index 19634a73612d..220fd0a6e175 100644 --- a/arch/x86/include/asm/unwind_user.h +++ b/arch/x86/include/asm/unwind_user.h @@ -2,41 +2,21 @@ #ifndef _ASM_X86_UNWIND_USER_H #define _ASM_X86_UNWIND_USER_H -#include <linux/unwind_user_types.h> - -#define ARCH_INIT_USER_FP_FRAME \ - .cfa_off = (s32)sizeof(long) * 2, \ - .ra_off = (s32)sizeof(long) * -1, \ - .fp_off = (s32)sizeof(long) * -2, \ - .use_fp = true, - #ifdef CONFIG_IA32_EMULATION - -#define ARCH_INIT_USER_COMPAT_FP_FRAME \ - .cfa_off = (s32)sizeof(u32) * 2, \ - .ra_off = (s32)sizeof(u32) * -1, \ - .fp_off = (s32)sizeof(u32) * -2, \ - .use_fp = true, - -#define in_compat_mode(regs) !user_64bit_mode(regs) - -void arch_unwind_user_init(struct unwind_user_state *state, - struct pt_regs *regs); - -static inline void arch_unwind_user_next(struct unwind_user_state *state) +/* Currently compat mode is not supported for deferred stack trace */ +static inline bool arch_unwind_can_defer(void) { - if (state->type != UNWIND_USER_TYPE_COMPAT_FP) - return; + struct pt_regs *regs = task_pt_regs(current); - state->ip += state->arch.cs_base; - state->fp += state->arch.ss_base; + return user_64bit_mode(regs); } - -#define arch_unwind_user_init arch_unwind_user_init -#define arch_unwind_user_next arch_unwind_user_next - +# define arch_unwind_can_defer arch_unwind_can_defer #endif /* CONFIG_IA32_EMULATION */ -#include <asm-generic/unwind_user.h> +#define ARCH_INIT_USER_FP_FRAME \ + .cfa_off = (s32)sizeof(long) * 2, \ + .ra_off = (s32)sizeof(long) * -1, \ + .fp_off = (s32)sizeof(long) * -2, \ + .use_fp = true, #endif /* _ASM_X86_UNWIND_USER_H */ diff --git a/arch/x86/include/asm/unwind_user_types.h b/arch/x86/include/asm/unwind_user_types.h deleted file mode 100644 index f93d535f900e..000000000000 --- a/arch/x86/include/asm/unwind_user_types.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_X86_UNWIND_USER_TYPES_H -#define _ASM_X86_UNWIND_USER_TYPES_H - -#ifdef CONFIG_IA32_EMULATION - -struct arch_unwind_user_state { - unsigned long ss_base; - unsigned long cs_base; -}; -#define arch_unwind_user_state arch_unwind_user_state - -#endif /* CONFIG_IA32_EMULATION */ - -#include <asm-generic/unwind_user_types.h> - -#endif /* _ASM_UNWIND_USER_TYPES_H */ diff --git a/arch/x86/kernel/stacktrace.c b/arch/x86/kernel/stacktrace.c index 8ef9d8c71df9..ee117fcf46ed 100644 --- a/arch/x86/kernel/stacktrace.c +++ b/arch/x86/kernel/stacktrace.c @@ -9,10 +9,7 @@ #include <linux/stacktrace.h> #include <linux/export.h> #include <linux/uaccess.h> -#include <asm/unwind_user.h> #include <asm/stacktrace.h> -#include <asm/insn.h> -#include <asm/insn-eval.h> #include <asm/unwind.h> void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie, @@ -131,28 +128,3 @@ void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie, } } -#ifdef CONFIG_IA32_EMULATION -void arch_unwind_user_init(struct unwind_user_state *state, - struct pt_regs *regs) -{ - unsigned long cs_base, ss_base; - - if (state->type != UNWIND_USER_TYPE_COMPAT_FP) - return; - - cs_base = insn_get_seg_base(regs, INAT_SEG_REG_CS); - ss_base = insn_get_seg_base(regs, INAT_SEG_REG_SS); - - if (cs_base == -1) - cs_base = 0; - if (ss_base == -1) - ss_base = 0; - - state->arch.cs_base = cs_base; - state->arch.ss_base = ss_base; - - state->ip += cs_base; - state->sp += ss_base; - state->fp += ss_base; -} -#endif /* CONFIG_IA32_EMULATION */ diff --git a/include/asm-generic/Kbuild b/include/asm-generic/Kbuild index b797a2434396..295c94a3ccc1 100644 --- a/include/asm-generic/Kbuild +++ b/include/asm-generic/Kbuild @@ -60,7 +60,6 @@ mandatory-y += topology.h mandatory-y += trace_clock.h mandatory-y += uaccess.h mandatory-y += unwind_user.h -mandatory-y += unwind_user_types.h mandatory-y += vermagic.h mandatory-y += vga.h mandatory-y += video.h diff --git a/include/asm-generic/unwind_user_types.h b/include/asm-generic/unwind_user_types.h deleted file mode 100644 index f568b82e52cd..000000000000 --- a/include/asm-generic/unwind_user_types.h +++ /dev/null @@ -1,5 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_GENERIC_UNWIND_USER_TYPES_H -#define _ASM_GENERIC_UNWIND_USER_TYPES_H - -#endif /* _ASM_GENERIC_UNWIND_USER_TYPES_H */ diff --git a/include/linux/srcu.h b/include/linux/srcu.h index 900b0d5c05f5..879054b8bf87 100644 --- a/include/linux/srcu.h +++ b/include/linux/srcu.h @@ -524,4 +524,8 @@ DEFINE_LOCK_GUARD_1(srcu, struct srcu_struct, srcu_read_unlock(_T->lock, _T->idx), int idx) +DEFINE_LOCK_GUARD_1(srcu_lite, struct srcu_struct, + _T->idx = srcu_read_lock_lite(_T->lock), + srcu_read_unlock_lite(_T->lock, _T->idx), + int idx) #endif diff --git a/include/linux/unwind_deferred.h b/include/linux/unwind_deferred.h index a9d5b100d6b2..0124865aaab4 100644 --- a/include/linux/unwind_deferred.h +++ b/include/linux/unwind_deferred.h @@ -16,18 +16,23 @@ struct unwind_work { int bit; }; -#ifdef CONFIG_UNWIND_USER +/* Architectures can add a test to not defer unwinding */ +#ifndef arch_unwind_can_defer +# define arch_unwind_can_defer() (true) +#endif -#define UNWIND_PENDING_BIT (BITS_PER_LONG - 1) -#define UNWIND_PENDING BIT(UNWIND_PENDING_BIT) +#ifdef CONFIG_UNWIND_USER -/* Set if the unwinding was used (directly or deferred) */ -#define UNWIND_USED_BIT (UNWIND_PENDING_BIT - 1) -#define UNWIND_USED BIT(UNWIND_USED_BIT) +enum { + UNWIND_PENDING_BIT = 0, + UNWIND_USED_BIT, +}; enum { - UNWIND_ALREADY_PENDING = 1, - UNWIND_ALREADY_EXECUTED = 2, + UNWIND_PENDING = BIT(UNWIND_PENDING_BIT), + + /* Set if the unwinding was used (directly or deferred) */ + UNWIND_USED = BIT(UNWIND_USED_BIT) }; void unwind_task_init(struct task_struct *task); @@ -56,8 +61,10 @@ static __always_inline void unwind_reset_info(void) } while (!try_cmpxchg(&info->unwind_mask, &bits, 0UL)); current->unwind_info.id.id = 0; - if (unlikely(info->cache)) + if (unlikely(info->cache)) { info->cache->nr_entries = 0; + info->cache->unwind_completed = 0; + } } } diff --git a/include/linux/unwind_deferred_types.h b/include/linux/unwind_deferred_types.h index db6c65daf185..33b62ac25c86 100644 --- a/include/linux/unwind_deferred_types.h +++ b/include/linux/unwind_deferred_types.h @@ -3,11 +3,24 @@ #define _LINUX_UNWIND_USER_DEFERRED_TYPES_H struct unwind_cache { + unsigned long unwind_completed; unsigned int nr_entries; unsigned long entries[]; }; - +/* + * The unwind_task_id is a unique identifier that maps to a user space + * stacktrace. It is generated the first time a deferred user space + * stacktrace is requested after a task has entered the kerenl and + * is cleared to zero when it exits. The mapped id will be a non-zero + * number. + * + * To simplify the generation of the 64 bit number, 32 bits will be + * the CPU it was generated on, and the other 32 bits will be a per + * cpu counter that gets incremented by two every time a new identifier + * is generated. The LSB will always be set to keep the value + * from being zero. + */ union unwind_task_id { struct { u32 cpu; @@ -17,9 +30,9 @@ union unwind_task_id { }; struct unwind_task_info { + unsigned long unwind_mask; struct unwind_cache *cache; struct callback_head work; - unsigned long unwind_mask; union unwind_task_id id; }; diff --git a/include/linux/unwind_user.h b/include/linux/unwind_user.h index 8a4af0214ecb..7f7282516bf5 100644 --- a/include/linux/unwind_user.h +++ b/include/linux/unwind_user.h @@ -9,31 +9,6 @@ #define ARCH_INIT_USER_FP_FRAME #endif -#ifndef ARCH_INIT_USER_COMPAT_FP_FRAME - #define ARCH_INIT_USER_COMPAT_FP_FRAME - #define in_compat_mode(regs) false -#endif - -/* - * If an architecture needs to initialize the state for a specific - * reason, for example, it may need to do something different - * in compat mode, it can define a macro named arch_unwind_user_init - * with the name of the function that will perform this initialization. - */ -#ifndef arch_unwind_user_init -static inline void arch_unwind_user_init(struct unwind_user_state *state, struct pt_regs *reg) {} -#endif - -/* - * If an architecture requires some more updates to the state between - * stack frames, it can define a macro named arch_unwind_user_next - * with the name of the function that will update the state between - * reading stack frames during the user space stack walk. - */ -#ifndef arch_unwind_user_next -static inline void arch_unwind_user_next(struct unwind_user_state *state) {} -#endif - int unwind_user(struct unwind_stacktrace *trace, unsigned int max_entries); #endif /* _LINUX_UNWIND_USER_H */ diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h index 0b6563951ca4..a449f15be890 100644 --- a/include/linux/unwind_user_types.h +++ b/include/linux/unwind_user_types.h @@ -3,16 +3,21 @@ #define _LINUX_UNWIND_USER_TYPES_H #include <linux/types.h> -#include <asm/unwind_user_types.h> -#ifndef arch_unwind_user_state -struct arch_unwind_user_state {}; -#endif +/* + * Unwind types, listed in priority order: lower numbers are attempted first if + * available. + */ +enum unwind_user_type_bits { + UNWIND_USER_TYPE_FP_BIT = 0, + + NR_UNWIND_USER_TYPE_BITS, +}; enum unwind_user_type { - UNWIND_USER_TYPE_NONE, - UNWIND_USER_TYPE_FP, - UNWIND_USER_TYPE_COMPAT_FP, + /* Type "none" for the start of stack walk iteration. */ + UNWIND_USER_TYPE_NONE = 0, + UNWIND_USER_TYPE_FP = BIT(UNWIND_USER_TYPE_FP_BIT), }; struct unwind_stacktrace { @@ -28,12 +33,12 @@ struct unwind_user_frame { }; struct unwind_user_state { - unsigned long ip; - unsigned long sp; - unsigned long fp; - struct arch_unwind_user_state arch; - enum unwind_user_type type; - bool done; + unsigned long ip; + unsigned long sp; + unsigned long fp; + enum unwind_user_type current_type; + unsigned int available_types; + bool done; }; #endif /* _LINUX_UNWIND_USER_TYPES_H */ diff --git a/kernel/unwind/deferred.c b/kernel/unwind/deferred.c index 039e12700d49..9972096e93e8 100644 --- a/kernel/unwind/deferred.c +++ b/kernel/unwind/deferred.c @@ -44,7 +44,11 @@ static inline bool try_assign_cnt(struct unwind_task_info *info, u32 cnt) /* Guards adding to or removing from the list of callbacks */ static DEFINE_MUTEX(callback_mutex); static LIST_HEAD(callbacks); -static unsigned long unwind_mask; + +#define RESERVED_BITS (UNWIND_PENDING | UNWIND_USED) + +/* Zero'd bits are available for assigning callback users */ +static unsigned long unwind_mask = RESERVED_BITS; DEFINE_STATIC_SRCU(unwind_srcu); static inline bool unwind_pending(struct unwind_task_info *info) @@ -73,19 +77,16 @@ static DEFINE_PER_CPU(u32, unwind_ctx_ctr); */ static u64 get_cookie(struct unwind_task_info *info) { - u32 cpu_cnt; - u32 cnt; + u32 cnt = 1; if (info->id.cpu) return info->id.id; - cpu_cnt = __this_cpu_read(unwind_ctx_ctr); - cpu_cnt += 2; - cnt = cpu_cnt | 1; /* Always make non zero */ - + /* LSB is always set to ensure 0 is an invalid value */ + cnt |= __this_cpu_read(unwind_ctx_ctr) + 2; if (try_assign_cnt(info, cnt)) { /* Update the per cpu counter */ - __this_cpu_write(unwind_ctx_ctr, cpu_cnt); + __this_cpu_write(unwind_ctx_ctr, cnt); } /* Interrupts are disabled, the CPU will always be same */ info->id.cpu = smp_processor_id() + 1; /* Must be non zero */ @@ -153,16 +154,13 @@ static void process_unwind_deferred(struct task_struct *task) struct unwind_work *work; unsigned long bits; u64 cookie; - int idx; if (WARN_ON_ONCE(!unwind_pending(info))) return; /* Clear pending bit but make sure to have the current bits */ - bits = READ_ONCE(info->unwind_mask); - while (!try_cmpxchg(&info->unwind_mask, &bits, bits & ~UNWIND_PENDING)) - ; - + bits = atomic_long_fetch_andnot(UNWIND_PENDING, + (atomic_long_t *)&info->unwind_mask); /* * From here on out, the callback must always be called, even if it's * just an empty trace. @@ -172,15 +170,20 @@ static void process_unwind_deferred(struct task_struct *task) unwind_user_faultable(&trace); + if (info->cache) + bits &= ~(info->cache->unwind_completed); + cookie = info->id.id; - idx = srcu_read_lock(&unwind_srcu); + guard(srcu_lite)(&unwind_srcu); list_for_each_entry_srcu(work, &callbacks, list, srcu_read_lock_held(&unwind_srcu)) { - if (test_bit(work->bit, &bits)) + if (test_bit(work->bit, &bits)) { work->func(work, &trace, cookie); + if (info->cache) + info->cache->unwind_completed |= BIT(work->bit); + } } - srcu_read_unlock(&unwind_srcu, idx); } static void unwind_deferred_task_work(struct callback_head *head) @@ -201,7 +204,7 @@ void unwind_deferred_task_exit(struct task_struct *task) } /** - * unwind_deferred_request - Request a user stacktrace on task exit + * unwind_deferred_request - Request a user stacktrace on task kernel exit * @work: Unwind descriptor requesting the trace * @cookie: The cookie of the first request made for this task * @@ -221,9 +224,7 @@ void unwind_deferred_task_exit(struct task_struct *task) * it will be called again with the same stack trace and cookie. * * Return: 0 if the callback successfully was queued. - * UNWIND_ALREADY_PENDING if the the callback was already queued. - * UNWIND_ALREADY_EXECUTED if the callback was already called - * (and will not be called again) + * 1 if the callback is pending or was already executed. * Negative if there's an error. * @cookie holds the cookie of the first request by any user */ @@ -231,17 +232,24 @@ int unwind_deferred_request(struct unwind_work *work, u64 *cookie) { struct unwind_task_info *info = ¤t->unwind_info; unsigned long old, bits; - int bit; + unsigned long bit; int ret; *cookie = 0; + if (!arch_unwind_can_defer()) + return -EINVAL; + if ((current->flags & (PF_KTHREAD | PF_EXITING)) || !user_mode(task_pt_regs(current))) return -EINVAL; - /* NMI requires having safe cmpxchg operations */ - if (!CAN_USE_IN_NMI && in_nmi()) + /* + * NMI requires having safe cmpxchg operations. + * Trigger a warning to make it obvious that an architecture + * is using this in NMI when it should not be. + */ + if (WARN_ON_ONCE(!CAN_USE_IN_NMI && in_nmi())) return -EINVAL; /* Do not allow cancelled works to request again */ @@ -249,44 +257,34 @@ int unwind_deferred_request(struct unwind_work *work, u64 *cookie) if (WARN_ON_ONCE(bit < 0)) return -EINVAL; + /* Only need the mask now */ + bit = BIT(bit); + guard(irqsave)(); *cookie = get_cookie(info); old = READ_ONCE(info->unwind_mask); - /* Is this already queued */ - if (test_bit(bit, &old)) { - /* - * If pending is not set, it means this work's callback - * was already called. - */ - return old & UNWIND_PENDING ? UNWIND_ALREADY_PENDING : - UNWIND_ALREADY_EXECUTED; - } - - if (unwind_pending(info)) - goto out; - - /* - * This is the first to enable another task_work for this task since - * the task entered the kernel, or had already called the callbacks. - * Set only the bit for this work and clear all others as they have - * already had their callbacks called, and do not need to call them - * again because of this work. - */ - bits = UNWIND_PENDING | BIT(bit); + /* Is this already queued or executed */ + if (old & bit) + return 1; /* - * If the cmpxchg() fails, it means that an NMI came in and set - * the pending bit as well as cleared the other bits. Just - * jump to setting the bit for this work. + * This work's bit hasn't been set yet. Now set it with the PENDING + * bit and fetch the current value of unwind_mask. If ether the + * work's bit or PENDING was already set, then this is already queued + * to have a callback. */ - if (CAN_USE_IN_NMI) { - if (!try_cmpxchg(&info->unwind_mask, &old, bits)) - goto out; - } else { - info->unwind_mask = bits; + bits = UNWIND_PENDING | bit; + old = atomic_long_fetch_or(bits, (atomic_long_t *)&info->unwind_mask); + if (old & bits) { + /* + * If the work's bit was set, whatever set it had better + * have also set pending and queued a callback. + */ + WARN_ON_ONCE(!(old & UNWIND_PENDING)); + return old & bit; } /* The work has been claimed, now schedule it. */ @@ -296,9 +294,6 @@ int unwind_deferred_request(struct unwind_work *work, u64 *cookie) WRITE_ONCE(info->unwind_mask, 0); return ret; - out: - return test_and_set_bit(bit, &info->unwind_mask) ? - UNWIND_ALREADY_PENDING : 0; } void unwind_deferred_cancel(struct unwind_work *work) @@ -309,9 +304,14 @@ void unwind_deferred_cancel(struct unwind_work *work) if (!work) return; + bit = work->bit; + + /* No work should be using a reserved bit */ + if (WARN_ON_ONCE(BIT(bit) & RESERVED_BITS)) + return; + guard(mutex)(&callback_mutex); list_del_rcu(&work->list); - bit = work->bit; /* Do not allow any more requests and prevent callbacks */ work->bit = -1; @@ -324,6 +324,8 @@ void unwind_deferred_cancel(struct unwind_work *work) /* Clear this bit from all threads */ for_each_process_thread(g, t) { clear_bit(bit, &t->unwind_info.unwind_mask); + if (t->unwind_info.cache) + clear_bit(bit, &t->unwind_info.cache->unwind_completed); } } @@ -334,7 +336,7 @@ int unwind_deferred_init(struct unwind_work *work, unwind_callback_t func) guard(mutex)(&callback_mutex); /* See if there's a bit in the mask available */ - if (unwind_mask == ~(UNWIND_PENDING|UNWIND_USED)) + if (unwind_mask == ~0UL) return -EBUSY; work->bit = ffz(unwind_mask); diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c index 249d9e32fad7..85b8c764d2f7 100644 --- a/kernel/unwind/user.c +++ b/kernel/unwind/user.c @@ -12,54 +12,18 @@ static struct unwind_user_frame fp_frame = { ARCH_INIT_USER_FP_FRAME }; -static struct unwind_user_frame compat_fp_frame = { - ARCH_INIT_USER_COMPAT_FP_FRAME -}; - -static inline bool fp_state(struct unwind_user_state *state) -{ - return IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP) && - state->type == UNWIND_USER_TYPE_FP; -} - #define for_each_user_frame(state) \ for (unwind_user_start(state); !(state)->done; unwind_user_next(state)) -static inline bool compat_fp_state(struct unwind_user_state *state) +static int unwind_user_next_fp(struct unwind_user_state *state) { - return IS_ENABLED(CONFIG_HAVE_UNWIND_USER_COMPAT_FP) && - state->type == UNWIND_USER_TYPE_COMPAT_FP; -} - -#define unwind_get_user_long(to, from, state) \ -({ \ - int __ret; \ - if (compat_fp_state(state)) \ - __ret = get_user(to, (u32 __user *)(from)); \ - else \ - __ret = get_user(to, (unsigned long __user *)(from)); \ - __ret; \ -}) - -static int unwind_user_next(struct unwind_user_state *state) -{ - struct unwind_user_frame *frame; - unsigned long cfa = 0, fp, ra = 0; + struct unwind_user_frame *frame = &fp_frame; + unsigned long cfa, fp, ra = 0; unsigned int shift; - if (state->done) - return -EINVAL; - - if (compat_fp_state(state)) - frame = &compat_fp_frame; - else if (fp_state(state)) - frame = &fp_frame; - else - goto done; - if (frame->use_fp) { if (state->fp < state->sp) - goto done; + return -EINVAL; cfa = state->fp; } else { cfa = state->sp; @@ -70,30 +34,53 @@ static int unwind_user_next(struct unwind_user_state *state) /* stack going in wrong direction? */ if (cfa <= state->sp) - goto done; + return -EINVAL; /* Make sure that the address is word aligned */ - shift = sizeof(long) == 4 || compat_fp_state(state) ? 2 : 3; - if ((cfa + frame->ra_off) & ((1 << shift) - 1)) - goto done; + shift = sizeof(long) == 4 ? 2 : 3; + if (cfa & ((1 << shift) - 1)) + return -EINVAL; /* Find the Return Address (RA) */ - if (unwind_get_user_long(ra, cfa + frame->ra_off, state)) - goto done; + if (get_user(ra, (unsigned long *)(cfa + frame->ra_off))) + return -EINVAL; - if (frame->fp_off && unwind_get_user_long(fp, cfa + frame->fp_off, state)) - goto done; + if (frame->fp_off && get_user(fp, (unsigned long __user *)(cfa + frame->fp_off))) + return -EINVAL; state->ip = ra; state->sp = cfa; if (frame->fp_off) state->fp = fp; + return 0; +} + +static int unwind_user_next(struct unwind_user_state *state) +{ + unsigned long iter_mask = state->available_types; + unsigned int bit; - arch_unwind_user_next(state); + if (state->done) + return -EINVAL; - return 0; + for_each_set_bit(bit, &iter_mask, NR_UNWIND_USER_TYPE_BITS) { + enum unwind_user_type type = BIT(bit); + + state->current_type = type; + switch (type) { + case UNWIND_USER_TYPE_FP: + if (!unwind_user_next_fp(state)) + return 0; + continue; + default: + WARN_ONCE(1, "Undefined unwind bit %d", bit); + break; + } + break; + } -done: + /* No successful unwind method. */ + state->current_type = UNWIND_USER_TYPE_NONE; state->done = true; return -EINVAL; } @@ -109,19 +96,13 @@ static int unwind_user_start(struct unwind_user_state *state) return -EINVAL; } - if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_COMPAT_FP) && in_compat_mode(regs)) - state->type = UNWIND_USER_TYPE_COMPAT_FP; - else if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP)) - state->type = UNWIND_USER_TYPE_FP; - else - state->type = UNWIND_USER_TYPE_NONE; + if (IS_ENABLED(CONFIG_HAVE_UNWIND_USER_FP)) + state->available_types |= UNWIND_USER_TYPE_FP; state->ip = instruction_pointer(regs); state->sp = user_stack_pointer(regs); state->fp = frame_pointer(regs); - arch_unwind_user_init(state, regs); - return 0; } -- Steve